home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2007 December
/
PCWKCD1207B.iso
/
Blogowanie poza sfera
/
Flock 1.0 beta
/
flock-1.0RC3.en-US.win32.exe
/
flock
/
components
/
nsSafebrowsingApplication.js
< prev
next >
Wrap
Text File
|
2007-10-18
|
148KB
|
4,034 lines
const Cc = Components.classes;
const Ci = Components.interfaces;
// This is copied from toolkit/components/content/js/lang.js.
// It seems cleaner to copy this rather than #include from so far away.
Function.prototype.inherits = function(parentCtor) {
var tempCtor = function(){};
tempCtor.prototype = parentCtor.prototype;
this.superClass_ = parentCtor.prototype;
this.prototype = new tempCtor();
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This file implements an event registrar, an object with which you
// can register handlers for arbitrary programmer-defined
// events. Events are arbitrary strings and listeners are functions
// taking an object (stuffed with arguments) as a parameter. When you
// fire an event through the registrar, all listeners are invoked in
// an unspecified order. The firing function takes an object to be
// passed into each handler (it is _not_ copied, so be careful). We
// chose this calling convention so we don't have to change handler
// signatures when adding new information.
//
// Why not just use notifier/observers? Because passing data around
// with them requires either serialization or a new xpcom interface,
// both of which are a pain in the ass.
//
// Example:
//
// // Set up a listener
// this.handleTabload = function(e) {
// foo(e.url);
// bar(e.browser);
// };
//
// // Set up the registrar
// var eventTypes = ["tabload", "tabunload", "tabswitch"];
// var registrar = new EventRegistrar(eventTypes);
// var handler = BindToObject(this.handleTabload, this);
//
// // Register a listener
// registrar.registerListener("tabload", handler);
//
// // Fire an event and remove the listener
// var event = { "url": "http://www", "browser": browser };
// registrar.fire("tabload", event);
// registrar.removeListener("tabload", handler);
//
// TODO: could add ability to cancel further handlers by having listeners
// return a boolean
/**
* EventRegistrars are used to manage user-defined events.
*
* @constructor
* @param eventTypes {Array or Object} Array holding names of events or
* Object holding properties the values of which are
* names (strings) for which listeners can register
*/
function EventRegistrar(eventTypes) {
this.eventTypes = [];
this.listeners_ = {}; // Listener sets, index by event type
if (eventTypes instanceof Array) {
var events = eventTypes;
} else if (typeof eventTypes == "object") {
var events = [];
for (var e in eventTypes)
events.push(eventTypes[e]);
} else {
throw new Error("Unrecognized init parameter to EventRegistrar");
}
for (var i = 0; i < events.length; i++) {
this.eventTypes.push(events[i]); // Copy in case caller mutates
this.listeners_[events[i]] =
new ListDictionary(events[i] + "Listeners");
}
}
/**
* Indicates whether the given event is one the registrar can handle.
*
* @param eventType {String} The name of the event to look up
* @returns {Boolean} false if the event type is not known or the
* event type string itself if it is
*/
EventRegistrar.prototype.isKnownEventType = function(eventType) {
for (var i=0; i < this.eventTypes.length; i++)
if (eventType == this.eventTypes[i])
return eventType;
return false;
}
/**
* Add an event type to listen for.
* @param eventType {String} The name of the event to add
*/
EventRegistrar.prototype.addEventType = function(eventType) {
if (this.isKnownEventType(eventType))
throw new Error("Event type already known: " + eventType);
this.eventTypes.push(eventType);
this.listeners_[eventType] = new ListDictionary(eventType + "Listeners");
}
/**
* Register to receive events of the type passed in.
*
* @param eventType {String} indicating the event type (one of this.eventTypes)
* @param listener {Function} to invoke when the event occurs.
*/
EventRegistrar.prototype.registerListener = function(eventType, listener) {
if (this.isKnownEventType(eventType) === false)
throw new Error("Unknown event type: " + eventType);
this.listeners_[eventType].addMember(listener);
}
/**
* Unregister a listener.
*
* @param eventType {String} One of EventRegistrar.eventTypes' members
* @param listener {Function} Function to remove as listener
*/
EventRegistrar.prototype.removeListener = function(eventType, listener) {
if (this.isKnownEventType(eventType) === false)
throw new Error("Unknown event type: " + eventType);
this.listeners_[eventType].removeMember(listener);
}
/**
* Invoke the handlers for the given eventType.
*
* @param eventType {String} The event to fire
* @param e {Object} Object containing the parameters of the event
*/
EventRegistrar.prototype.fire = function(eventType, e) {
if (this.isKnownEventType(eventType) === false)
throw new Error("Unknown event type: " + eventType);
var invoke = function(listener) {
listener(e);
};
this.listeners_[eventType].forEach(invoke);
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This file implements a Dictionary data structure using a list
// (array). We could instead use an object, but using a list enables
// us to have ordering guarantees for iterators. The interface it exposes
// is:
//
// addMember(item)
// removeMember(item)
// isMember(item)
// forEach(func)
//
// TODO: this class isn't really a Dictionary, it's more like a
// membership set (i.e., a set without union and whatnot). We
// should probably change the name to avoid confusion.
/**
* Create a new Dictionary data structure.
*
* @constructor
* @param name A string used to name the dictionary
*/
function ListDictionary(name) {
this.name_ = name;
this.members_ = [];
}
/**
* Look an item up.
*
* @param item An item to look up in the dictionary
* @returns Boolean indicating if the parameter is a member of the dictionary
*/
ListDictionary.prototype.isMember = function(item) {
for (var i=0; i < this.members_.length; i++)
if (this.members_[i] == item)
return true;
return false;
}
/**
* Add an item
*
* @param item An item to add (does not check for dups)
*/
ListDictionary.prototype.addMember = function(item) {
this.members_.push(item);
}
/**
* Remove an item
*
* @param item The item to remove (doesn't check for dups)
* @returns Boolean indicating if the item was removed
*/
ListDictionary.prototype.removeMember = function(item) {
for (var i=0; i < this.members_.length; i++) {
if (this.members_[i] == item) {
for (var j=i; j < this.members_.length; j++)
this.members_[j] = this.members_[j+1];
this.members_.length--;
return true;
}
}
return false;
}
/**
* Apply a function to each of the members. Does NOT replace the members
* in the dictionary with results -- it just calls the function on each one.
*
* @param func Function to apply to the dictionary's members
*/
ListDictionary.prototype.forEach = function(func) {
if (typeof func != "function")
throw new Error("argument to forEach is not a function, it's a(n) " +
typeof func);
for (var i=0; i < this.members_.length; i++)
func(this.members_[i]);
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This file implements a G_TabbedBrowserWatcher, an object
// encapsulating and abstracting the mechanics of working with tabs
// and the documents within them. The watcher provides notification of
// various DOM-related events (a document loaded, a document unloaded,
// tab was created/destroyed, user switched tabs, etc.) as well as
// commonly required methods (get me the current tab, find the tab to
// which this document belongs, etc.).
//
// This class does not do progresslistener-based notifications; for that,
// use the NavWatcher.
//
// Note: I use "browser" and "tab" interchangeably.
//
// This class adds a level of indirection to event registration. You
// initialize it with a tabbedbrowser, and then register to hear
// events from it instead of from the tabbedbrowser or browser itself.
// Your handlers are invoked with a custom object as an argument (see
// below). This object contains useful information such as a reference
// to the browser in which the event is happening and whether the
// event is occurring on the top-level document in that browser.
//
// The events you can register to hear are:
//
// EVENT DESCRIPTION
// ----- -----------
// load Fires when a page is shown in the browser window and
// this page wasn't in the bfcache
//
// unload Fires when a page is nav'd away from in the browser window
// and the page isn't going into the bfcache
//
// pageshow Fires when a page is shown in the browser window, whether
// it came from bfcache or not. (There is a "persisted"
// property we can get from the event object if we'd like.
// It indicates whether the page was loaded from bfcache.
// If false then we known load recently fired).
//
// pagehide Fires when a page is nav'd away from in the browser,
// whether its going into the bfcache or not. (There is
// a persisted property here as well that we're not
// propagating -- when its true the page is going into
// the bfcache, else it's not, and unload will shortly
// fire).
//
// domcontentloaded BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN
// Fires when a doc's DOM is ready, but its externally linked
// content hasn't necessarily loaded. This event is
// currently broken: it doesn't fire when using the
// forward/back buttons in conjunction with the bfcache.
// Bryner is working on a fix.
//
// tabload Fires when a new tab has been created (but doesn't
// necessarily have content loaded in it)
//
// tabunload Fires when a tab is being destroyed (and might have had
// the content it contains destroyed)
//
// tabswitch Fires when the user switches tabs
//
// tabmove Fires when a user drags a tab to a different position
//
//
// For pageshow, pagehide, load, unload, and domcontentloaded, the event
// object you'll receive has the following properties:
//
// doc -- reference to Document on which the event fired
// browser -- the browser in which the document resides
// isTop -- boolean indicating if it is the top-level document
// inSelected -- boolean indicating if it is in the currently selected tab
//
// For tabload and unload it has:
//
// browser -- reference to the browser that is loading or closing
//
// For tabswitch it has:
//
// fromBrowser -- reference to the browser user is switching from
// toBrowser -- reference to the browser user is switching to
//
// For tabmove it has:
//
// tab -- the tab that was moved (Note that this is the actual
// tab widget that holds the document title, not the
// browser object. We use this because it's the target
// of the DOMNodeInserted event.)
// fromIndex -- the tab index before the move
// toIndex -- the tab index after the move
//
//
// The order of events is: tabload
// domcontentloaded
// load
// pageshow
// -- --
// pagehide
// unload
// tabunload
//
// Example:
//
// function handler(e /*event object*/) {
// foo(e.browser);
// };
// var watcher = new G_TabbedBrowserWatcher(document.getElementById(gBrowser));
// watcher.registerListener("load", handler); // register for events
// watcher.removeListener("load", handler); // unregister
//
//
// TODO, BUGS, ISSUES, COMPLICATIONS:
//
// The only major problem is in closing tabs:
//
// + When you close a tab, the reference to the Document you get in the
// unload event is undefined. We pass this along. This could easily
// be fixed by not listening for unload at all, but instead inferring
// it from the information in pagehide, and then firing our own "fake"
// unload after firing pagehide.
//
// + There's no docshell during the pagehide event, so we can't determine
// if the document is top-level. We pass undefined in this case.
//
// + Though the browser reference during tabunload will be valid, its
// members most likely have already been torn down. Use it in an
// objectsafemap to keep state if you need its members.
//
// + The event listener DOMNodeInserted has the potential to cause
// performance problems if there are too many events fired. It
// should be ok, since we inserted it as far as possible into
// the xul tree.
//
//
// TODO: need better enforcement of unique names. Two tabbedbrowserwatchers
// with the same name will clobber each other because they use that
// name to mark browsers they've seen.
//
// TODO: the functions that iterate of windows and documents badly need
// to be made cleaner. Right now we have multiple implementations
// that essentially do the same thing :(
//
// But good enough for government work.
/**
* Encapsulates tab-related information. You can use the
* G_TabbedBrowserWatcher to watch for events on tabs as well as to
* retrieve tab-related data (such as what tab is currently showing).
* It receives many event notifications from G_BrowserWatchers it
* attaches to newly opening tabs.
*
* @param tabBrowser A reference to the tabbed browser you wish to watch.
*
* @param name String containing a probabilistically unique name. Used to
* ensure that each tabbedbrowserwatcher can uniquely mark
* browser it has "seen."
*
* @param opt_filterAboutBlank Boolean indicating whether to filter events
* for about:blank. These events are often
* spurious since about:blank is the default
* page for an empty browser.
*
* @constructor
*/
function G_TabbedBrowserWatcher(tabBrowser, name, opt_filterAboutBlank) {
this.debugZone = "tabbedbrowserwatcher";
this.registrar_ = new EventRegistrar(G_TabbedBrowserWatcher.events);
this.tabBrowser_ = tabBrowser;
this.filterAboutBlank_ = !!opt_filterAboutBlank;
this.events = G_TabbedBrowserWatcher.events; // Convenience pointer
// We need some way to tell if we've seen a browser before, so we
// set a property on it with a probabilistically unique string. The
// string is a combination of a static string and one passed in by
// the caller.
G_Assert(this, typeof name == "string" && !!name,
"Need a probabilistically unique name");
this.name_ = name;
this.mark_ = G_TabbedBrowserWatcher.mark_ + "-" + this.name_;
this.tabbox_ = this.getTabBrowser().mTabBox;
// There's no tabswitch event in Firefox, so we fake it by watching
// for selects on the tabbox.
this.onTabSwitchClosure_ = BindToObject(this.onTabSwitch, this);
this.tabbox_.addEventListener("select",
this.onTabSwitchClosure_, true);
// Used to determine when the user has switched tabs
this.lastTab_ = this.getCurrentBrowser();
}
// Events for which listeners can register
G_TabbedBrowserWatcher.events = {
TABSWITCH: "tabswitch",
};
// We mark new tabs as we see them
G_TabbedBrowserWatcher.mark_ = "watcher-marked";
/**
* Remove all the event handlers and clean up circular refs.
*/
G_TabbedBrowserWatcher.prototype.shutdown = function() {
G_Debug(this, "Removing event listeners");
if (this.tabbox_) {
this.tabbox_.removeEventListener("select",
this.onTabSwitchClosure_, true);
// Break circular ref so we can be gc'ed.
this.tabbox_ = null;
}
// Break circular ref so we can be gc'ed.
if (this.lastTab_) {
this.lastTab_ = null;
}
if (this.tabBrowser_) {
this.tabBrowser_ = null;
}
}
/**
* Check to see if we've seen a browser before
*
* @param browser Browser to check
* @returns Boolean indicating if we've attached a BrowserWatcher to this
* browser
*/
G_TabbedBrowserWatcher.prototype.isInstrumented_ = function(browser) {
return !!browser[this.mark_];
}
/**
* Attaches a BrowserWatcher to a browser and marks it as seen
*
* @param browser Browser to which to attach a G_BrowserWatcher
*/
G_TabbedBrowserWatcher.prototype.instrumentBrowser_ = function(browser) {
G_Assert(this, !this.isInstrumented_(browser),
"Browser already instrumented!");
// The browserwatcher will hook itself into the browser and its parent (us)
new G_BrowserWatcher(this, browser);
browser[this.mark_] = true;
}
/**
* Register to receive events of a particular type
*
* @param eventType String indicating the event (see
* G_TabbedBrowserWatcher.events)
* @param listener Function to invoke when the event occurs. See top-
* level comments for parameters.
*/
G_TabbedBrowserWatcher.prototype.registerListener = function(eventType,
listener) {
this.registrar_.registerListener(eventType, listener);
}
/**
* Unregister a listener.
*
* @param eventType String one of G_TabbedBrowserWatcher.events' members
* @param listener Function to remove as listener
*/
G_TabbedBrowserWatcher.prototype.removeListener = function(eventType,
listener) {
this.registrar_.removeListener(eventType, listener);
}
/**
* Send an event to all listeners for that type.
*
* @param eventType String indicating the event to trigger
* @param e Object to pass to each listener (NOT copied -- be careful)
*/
G_TabbedBrowserWatcher.prototype.fire = function(eventType, e) {
this.registrar_.fire(eventType, e);
}
/**
* Convenience function to send a document-related event. We use this
* convenience function because the event constructing logic and
* parameters are the same for all these events. (Document-related
* events are load, unload, pagehide, pageshow, and domcontentloaded).
*
* @param eventType String indicating the type of event to fire (one of
* the document-related events)
*
* @param doc Reference to the HTMLDocument the event is occuring to
*
* @param browser Reference to the browser in which the document is contained
*/
G_TabbedBrowserWatcher.prototype.fireDocEvent_ = function(eventType,
doc,
browser) {
// If we've already shutdown, don't bother firing any events.
if (!this.tabBrowser_) {
G_Debug(this, "Firing event after shutdown: " + eventType);
return;
}
try {
// Could be that the browser's contentDocument has already been torn
// down. If so, this throws, and we can't tell without keeping more
// state whether doc was the top frame.
var isTop = (doc == browser.contentDocument);
} catch(e) {
var isTop = undefined;
}
var inSelected = (browser == this.getCurrentBrowser());
var location = doc ? doc.location.href : undefined;
// Only send notifications for about:config's if we're supposed to
if (!this.filterAboutBlank_ || location != "about:blank") {
G_Debug(this, "firing " + eventType + " for " + location +
(isTop ? " (isTop)" : "") + (inSelected ? " (inSelected)" : ""));
this.fire(eventType, { "doc": doc,
"isTop": isTop,
"inSelected": inSelected,
"browser": browser});
}
}
/**
* Invoked when the user might have switched tabs
*
* @param e Event object
*/
G_TabbedBrowserWatcher.prototype.onTabSwitch = function(e) {
// Filter spurious events
// The event target is usually tabs but can be tabpanels when tabs were opened
// programatically via tabbrowser.addTab().
if (e.target == null ||
(e.target.localName != "tabs" && e.target.localName != "tabpanels"))
return;
var fromBrowser = this.lastTab_;
var toBrowser = this.getCurrentBrowser();
if (fromBrowser != toBrowser) {
this.lastTab_ = toBrowser;
G_Debug(this, "firing tabswitch");
this.fire(this.events.TABSWITCH, { "fromBrowser": fromBrowser,
"toBrowser": toBrowser });
}
}
// Utility functions
/**
* Returns a reference to the tabbed browser this G_TabbedBrowserWatcher
* was initialized with.
*/
G_TabbedBrowserWatcher.prototype.getTabBrowser = function() {
return this.tabBrowser_;
}
/**
* Returns a reference to the currently selected tab.
*/
G_TabbedBrowserWatcher.prototype.getCurrentBrowser = function() {
return this.getTabBrowser().selectedBrowser;
}
/**
* Returns a reference to the top window in the currently selected tab.
*/
G_TabbedBrowserWatcher.prototype.getCurrentWindow = function() {
return this.getCurrentBrowser().contentWindow;
}
/**
* Find the browser corresponding to a Document
*
* @param doc Document we want the browser for
* @returns Reference to the browser in which the given document is found
* or null if not found
*/
G_TabbedBrowserWatcher.prototype.getBrowserFromDocument = function(doc) {
// Could instead get the top window of the browser in which the doc
// is found via doc.defaultView.top, but sometimes the document
// isn't in a browser at all (it's being unloaded, for example), so
// defaultView won't be valid.
// Helper: return true if doc is a sub-document of win
function docInWindow(doc, win) {
if (win.document == doc)
return true;
if (win.frames)
for (var i = 0; i < win.frames.length; i++)
if (docInWindow(doc, win.frames[i]))
return true;
return false;
}
var browsers = this.getTabBrowser().browsers;
for (var i = 0; i < browsers.length; i++)
if (docInWindow(doc, browsers[i].contentWindow))
return browsers[i];
return null;
}
/**
* Find the Document that has the given URL loaded. Returns on the
* _first_ such document found, so be careful.
*
* TODO make doc/window searches more elegant, and don't use inner functions
*
* @param url String indicating the URL we're searching for
* @param opt_browser Optional reference to a browser. If given, the
* search will be confined to only this browser.
* @returns Reference to the Document with that URL or null if not found
*/
G_TabbedBrowserWatcher.prototype.getDocumentFromURL = function(url,
opt_browser) {
// Helper function: return the Document in win that has location of url
function docWithURL(win, url) {
if (win.document.location.href == url)
return win.document;
if (win.frames)
for (var i = 0; i < win.frames.length; i++) {
var rv = docWithURL(win.frames[i], url);
if (rv)
return rv;
}
return null;
}
if (opt_browser)
return docWithURL(opt_browser.contentWindow, url);
var browsers = this.getTabBrowser().browsers;
for (var i=0; i < browsers.length; i++) {
var rv = docWithURL(browsers[i].contentWindow, url);
if (rv)
return rv;
}
return null;
}
/**
* Find the all Documents that have the given URL loaded.
*
* TODO make doc/window searches more elegant, and don't use inner functions
*
* @param url String indicating the URL we're searching for
*
* @param opt_browser Optional reference to a browser. If given, the
* search will be confined to only this browser.
*
* @returns Array of Documents with the given URL (zero length if none found)
*/
G_TabbedBrowserWatcher.prototype.getDocumentsFromURL = function(url,
opt_browser) {
var docs = [];
// Helper function: add Docs in win with the location of url
function getDocsWithURL(win, url) {
if (win.document.location.href == url)
docs.push(win.document);
if (win.frames)
for (var i = 0; i < win.frames.length; i++)
getDocsWithURL(win.frames[i], url);
}
if (opt_browser)
return getDocsWithURL(opt_browser.contentWindow, url);
var browsers = this.getTabBrowser().browsers;
for (var i=0; i < browsers.length; i++)
getDocsWithURL(browsers[i].contentWindow, url);
return docs;
}
/**
* Finds the browser in which a Window resides.
*
* @param sub Window to find
* @returns Reference to the browser in which sub resides, else null
* if not found
*/
G_TabbedBrowserWatcher.prototype.getBrowserFromWindow = function(sub) {
// Helpfer function: return true if sub is a sub-window of win
function containsSubWindow(sub, win) {
if (win == sub)
return true;
if (win.frames)
for (var i=0; i < win.frames.length; i++)
if (containsSubWindow(sub, win.frames[i]))
return true;
return false;
}
var browsers = this.getTabBrowser().browsers;
for (var i=0; i < browsers.length; i++)
if (containsSubWindow(sub, browsers[i].contentWindow))
return browsers[i];
return null;
}
/**
* Finds the XUL <tab> tag corresponding to a given browser.
*
* @param tabBrowser Reference to the tabbed browser in which browser lives
* @param browser Reference to the browser we wish to find the tab of
* @returns Reference to the browser's tab element, or null
* @static
*/
G_TabbedBrowserWatcher.getTabElementFromBrowser = function(tabBrowser,
browser) {
for (var i=0; i < tabBrowser.browsers.length; i++)
if (tabBrowser.browsers[i] == browser)
return tabBrowser.mTabContainer.childNodes[i];
return null;
}
//@line 16 "/cygdrive/K/tinderbuild/src/flock/mozilla/browser/components/safebrowsing/src/nsSafebrowsingApplication.js"
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// We instantiate this variable when we create the application.
var gDataProvider = null;
// An instance of our application is a PROT_Application object. It
// basically just populates a few globals and instantiates wardens and
// the listmanager.
/**
* An instance of our application. There should be exactly one of these.
*
* Note: This object should instantiated only at profile-after-change
* or later because the listmanager and the cryptokeymanager need to
* read and write data files. Additionally, NSS isn't loaded until
* some time around then (Moz bug #321024).
*
* @constructor
*/
function PROT_Application() {
this.debugZone= "application";
//@line 88 "/cygdrive/K/tinderbuild/src/flock/mozilla/browser/components/safebrowsing/src/../content/application.js"
// expose some classes
this.G_TabbedBrowserWatcher = G_TabbedBrowserWatcher;
this.PROT_Controller = PROT_Controller;
this.PROT_PhishingWarden = PROT_PhishingWarden;
// Load data provider pref values
gDataProvider = new PROT_DataProvider();
// expose the object
this.wrappedJSObject = this;
}
/**
* @return String the report phishing URL (localized).
*/
PROT_Application.prototype.getReportPhishingURL = function() {
return gDataProvider.getReportPhishURL();
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// There is one BrowserView per browser window, and each BrowserView
// is responsible for keeping track of problems (phishy documents)
// within that window. The BrowserView is also responsible for
// figuring out what to do about such problems, for example, whether
// the tab with a phishy page is currently showing and therefore if we
// should be showing a warning.
//
// The BrowserView receives information from three places:
//
// - from the phishing warden. When the phishing warden notices a
// problem, it queries all browser views to see which one (if any)
// has the Document that is problematic. It then hands the problem
// off to the appropriate BrowserView.
//
// - from the controller. The controller responds to explicit user
// actions (tab switches, requests to hide the warning message,
// etc.) and let's the BrowserView know about any user action
// having to do with the problems it is tracking.
//
// - from the TabbedBrowserWatcher. When the BrowserView is keeping
// track of a problematic document it listens for interesting
// events affecting it, for example pagehide (at which point
// we presumably hide the warning if we're showing it).
//
// The BrowserView associates at most one "problem" with each Document
// in the browser window. It keeps state about which Documents are
// problematic by storing a "problem queue" on each browser (tab).
// At most one problematic document per browser (tab) is active
// at any time. That is, we show the warning for at most one phishy
// document at any one time. If another phishy doc loads in that tab,
// it goes onto the end of the queue to be activated only when the
// currently active document goes away.
//
// If we had multiple types of warnings (one for after the page had
// loaded, one for when the user clicked a link, etc) here's where
// we'd select the appropate one to use. As it stands, we only have
// one displayer (an "afterload" displayer). A displayer knows _how_
// to display a warning, whereas as the BrowserView knows _what_ and
// _when_.
//
// To keep things (relatively) easy to reason about and efficient (the
// phishwarden could be querying us inside a progresslistener
// notification, or the controller inside an event handler), we have
// the following rules:
//
// - at most one of a displayer's start() or stop() methods is called
// in any iteration (if calling two is required, the second is run in
// the next event loop)
// - displayers should run their operations synchronously so we don't have
// to look two places (here and in the displayer) to see what is happening
// when
// - displayer actions are run after cleaning up the browser view state
// in case they have consequences
//
// TODO: this could use some redesign, but I don't have time.
// TODO: the queue needs to be abstracted, but we want another release fast,
// so I'm not going to touch it for the time being
// TODO: IDN issues and canonical URLs?
// TODO: Perhaps we should blur the page before showing a warning in order
// to prevent stray keystrokes?
/**
* The BrowerView is responsible for keeping track of and figuring out
* what to do with problems within a single browser window.
*
* TODO
* Unify all browser-related state here. Currently it's split
* between two objects, this object and the controller. We could have
* this object be solely responsible for UI hide/show decisions, which
* would probably make it easier to reason about what's going on.
*
* TODO
* Investigate an alternative model. For example, we could factor out
* the problem signaling stuff from the tab/UI logic into a
* ProblemRegistry. Attach listeners to new docs/requests as they go
* by and have these listeners periodically check in with a
* ProblemRegistry to see if they're watching a problematic
* doc/request. If so, then have them flag the browser view to be
* aware of the problem.
*
* @constructor
* @param tabWatcher Reference to the TabbedBrowserWatcher we'll use to query
* for information about active tabs/browsers.
* @param doc Reference to the XUL Document (browser window) in which the
* tabwatcher is watching
*/
function PROT_BrowserView(tabWatcher, doc) {
this.debugZone = "browserview";
this.tabWatcher_ = tabWatcher;
this.doc_ = doc;
}
/**
* See if we have any Documents with a given (problematic) URL that
* haven't yet been marked as problems. Called as a subroutine by
* tryToHandleProblemRequest().
*
* @param url String containing the URL to look for
*
* @returns Reference to an unhandled Document with the problem URL or null
*/
PROT_BrowserView.prototype.getFirstUnhandledDocWithURL_ = function(url) {
var docs = this.tabWatcher_.getDocumentsFromURL(url);
if (!docs.length)
return null;
for (var i = 0; i < docs.length; i++) {
// We only care about top level documents (i.e., we don't care about
// frames).
if (docs[i].defaultView.top != docs[i].defaultView)
continue;
var browser = this.tabWatcher_.getBrowserFromDocument(docs[i]);
G_Assert(this, !!browser, "Found doc but can't find browser???");
var alreadyHandled = this.getProblem_(docs[i], browser);
if (!alreadyHandled)
return docs[i];
}
return null;
}
/**
* Invoked by the warden to give us the opportunity to handle a
* problem. A problem is signaled once per request for a problem
* Document and is handled at most once, so there's no issue with us
* "losing" a problem due to multiple concurrently loading Documents
* with the same URL.
*
* @param warden Reference to the warden signalling the problem. We'll
* need him to instantiate one of his warning displayers
*
* @param request The nsIRequest that is problematic
*
* @returns Boolean indicating whether we handled problem
*/
PROT_BrowserView.prototype.tryToHandleProblemRequest = function(warden,
request) {
var doc = this.getFirstUnhandledDocWithURL_(request.name);
if (doc) {
var browser = this.tabWatcher_.getBrowserFromDocument(doc);
G_Assert(this, !!browser, "Couldn't get browser from problem doc???");
G_Assert(this, !this.getProblem_(doc, browser),
"Doc is supposedly unhandled, but has state?");
this.isProblemDocument_(browser, doc, warden);
return true;
}
return false;
}
/**
* We're sure a particular Document is problematic, so let's instantiate
* a dispalyer for it and add it to the problem queue for the browser.
*
* @param browser Reference to the browser in which the problem doc resides
*
* @param doc Reference to the problematic document
*
* @param warden Reference to the warden signalling the problem.
*/
PROT_BrowserView.prototype.isProblemDocument_ = function(browser,
doc,
warden) {
G_Debug(this, "Document is problem: " + doc.location.href);
var url = doc.location.href;
// We only have one type of displayer right now
var displayer = new warden.displayers_["afterload"]("Phishing afterload",
browser,
this.doc_,
url);
// We listen for the problematic document being navigated away from
// so we can remove it from the problem queue
var hideHandler = BindToObject(this.onNavAwayFromProblem_,
this,
doc,
browser);
doc.defaultView.addEventListener("pagehide", hideHandler, true);
// More info than we technically need, but it comes in handy for debugging
var problem = {
"browser_": browser,
"doc_": doc,
"displayer_": displayer,
"url_": url,
"hideHandler_": hideHandler,
};
var numInQueue = this.queueProblem_(browser, problem);
// If the queue was empty, schedule us to take something out
if (numInQueue == 1)
new G_Alarm(BindToObject(this.unqueueNextProblem_, this, browser), 0);
}
/**
* Invoked when a problematic document is navigated away from.
*
* @param doc Reference to the problematic Document navigated away from
* @param browser Reference to the browser in which the problem document
* unloaded
*/
PROT_BrowserView.prototype.onNavAwayFromProblem_ = function(doc, browser) {
G_Debug(this, "User nav'd away from problem.");
var problem = this.getProblem_(doc, browser);
(new PROT_Reporter).report("phishnavaway", problem.url_);
G_Assert(this, doc === problem.doc_, "State doc not equal to nav away doc?");
G_Assert(this, browser === problem.browser_,
"State browser not equal to nav away browser?");
this.problemResolved(browser, doc);
}
/**
* @param browser Reference to a browser we'd like to know about
*
* @returns Boolean indicating if the browser in question has
* problematic content
*/
PROT_BrowserView.prototype.hasProblem = function(browser) {
return this.hasNonemptyProblemQueue_(browser);
}
/**
* @param browser Reference to a browser we'd like to know about
*
* @returns Boolean indicating if the browser in question has a
* problem (i.e., it has a non-empty problem queue)
*/
PROT_BrowserView.prototype.hasNonemptyProblemQueue_ = function(browser) {
try {
return !!browser.PROT_problemState__ &&
!!browser.PROT_problemState__.length;
} catch(e) {
// We could be checking a browser that has just been closed, in
// which case its properties will not be valid, causing the above
// statement to throw an error. Since this case handled elsewhere,
// just return false.
return false;
}
}
/**
* Invoked to indicate that the problem for a particular problematic
* document in a browser has been resolved (e.g., by being navigated
* away from).
*
* @param browser Reference to the browser in which resolution is happening
*
* @param opt_doc Reference to the problematic doc whose problem was resolved
* (if absent, assumes the doc assocaited with the currently
* active displayer)
*/
PROT_BrowserView.prototype.problemResolved = function(browser, opt_doc) {
var problem;
var doc;
if (!!opt_doc) {
doc = opt_doc;
problem = this.getProblem_(doc, browser);
} else {
problem = this.getCurrentProblem_(browser);
doc = problem.doc_;
}
problem.displayer_.done();
var wasHead = this.deleteProblemFromQueue_(doc, browser);
// Peek at the next problem (if any) in the queue for this browser
var queueNotEmpty = this.getCurrentProblem_(browser);
if (wasHead && queueNotEmpty) {
G_Debug(this, "More problems pending. Scheduling unqueue.");
new G_Alarm(BindToObject(this.unqueueNextProblem_, this, browser), 0);
}
}
/**
* Peek at the top of the problem queue and if there's something there,
* make it active.
*
* @param browser Reference to the browser we should activate a problem
* displayer in if one is available
*/
PROT_BrowserView.prototype.unqueueNextProblem_ = function(browser) {
var problem = this.getCurrentProblem_(browser);
if (!problem) {
G_Debug(this, "No problem in queue; doc nav'd away from? (shrug)");
return;
}
// Two problem docs that load in rapid succession could both schedule
// themselves to be unqueued before this method is called. So ensure
// that the problem at the head of the queue is not, in fact, active.
if (!problem.displayer_.isActive()) {
// It could be the case that the server is really slow to respond,
// so there might not yet be anything in the problem Document. If
// we show the warning when that's the case, the user will see a
// blank document greyed out, and if they cancel the dialog
// they'll see the page they're navigating away from because it
// hasn't been painted over yet (b/c there's no content for the
// problem page). So here we ensure that we have content for the
// problem page before showing the dialog.
var haveContent = false;
try {
// This will throw if there's no content yet
var h = problem.doc_.defaultView.getComputedStyle(problem.doc_.body, "")
.getPropertyValue("height");
G_Debug(this, "body height: " + h);
if (Number(h.substring(0, h.length - 2)))
haveContent = true;
} catch (e) {
G_Debug(this, "Masked in unqueuenextproblem: " + e);
}
if (!haveContent) {
G_Debug(this, "Didn't get computed style. Re-queueing.");
// One stuck problem document in a page shouldn't prevent us
// warning on other problem frames that might be loading or
// loaded. So stick the Document that doesn't have content
// back at the end of the queue.
var p = this.removeProblemFromQueue_(problem.doc_, browser);
G_Assert(this, p === problem, "Unqueued wrong problem?");
this.queueProblem_(browser, problem);
// Try again in a bit. This opens us up to a potential
// vulnerability (put tons of hanging frames in a page
// ahead of your real phishy frame), but the risk at the
// moment is really low (plus it is outside our threat
// model).
new G_Alarm(BindToObject(this.unqueueNextProblem_,
this,
browser),
200 /*ms*/);
return;
}
problem.displayer_.start();
// OK, we have content, but there there is an additional
// issue. Due to a bfcache bug, if we show the warning during
// paint suppression, the collapsing of the content pane affects
// the doc we're naving from :( The symptom is a page with grey
// screen on navigation to or from a phishing page (the
// contentDocument will have width zero).
//
// Paint supression lasts at most 250ms from when the parser sees
// the body, and the parser sees the body well before it has a
// height. We err on the side of caution.
//
// Thanks to bryner for helping to track the bfcache bug down.
// https://bugzilla.mozilla.org/show_bug.cgi?id=319646
if (this.tabWatcher_.getCurrentBrowser() === browser)
new G_Alarm(BindToObject(this.problemBrowserMaybeSelected,
this,
browser),
350 /*ms*/);
}
}
/**
* Helper function that adds a new problem to the queue of problems pending
* on this browser.
*
* @param browser Browser to which we should add state
*
* @param problem Object (structure, really) encapsulating the problem
*
* @returns Number indicating the number of items in the queue (and from
* which you can infer whether the recently added item was
* placed at the head, and hence should be active.
*/
PROT_BrowserView.prototype.queueProblem_ = function(browser, problem) {
G_Debug(this, "Adding problem state for " + problem.url_);
if (this.hasNonemptyProblemQueue_(browser))
G_Debug(this, "Already has problem state. Queueing this problem...");
// First problem ever signaled on this browser? Make a new queue!
if (browser.PROT_problemState__ == undefined)
browser.PROT_problemState__ = [];
browser.PROT_problemState__.push(problem);
return browser.PROT_problemState__.length;
}
/**
* Helper function that removes a problem from the queue and deactivates
* it.
*
* @param doc Reference to the doc for which we should remove state
*
* @param browser Reference to the browser from which we should remove
* state
*
* @returns Boolean indicating if the remove problem was currently active
* (that is, if it was at the head of the queue)
*/
PROT_BrowserView.prototype.deleteProblemFromQueue_ = function(doc, browser) {
G_Debug(this, "Deleting problem state for " + browser);
G_Assert(this, !!this.hasNonemptyProblemQueue_(browser),
"Browser has no problem state");
var problem = this.getProblem_(doc, browser);
G_Assert(this, !!problem, "Couldnt find state in removeproblemstate???");
var wasHead = browser.PROT_problemState__[0] === problem;
this.removeProblemFromQueue_(doc, browser);
var hideHandler = problem.hideHandler_;
G_Assert(this, !!hideHandler, "No hidehandler in state?");
problem.doc_.defaultView.removeEventListener("pagehide",
hideHandler,
true);
return wasHead;
}
/**
* Helper function that removes a problem from the queue but does
* NOT deactivate it.
*
* @param doc Reference to the doc for which we should remove state
*
* @param browser Reference to the browser from which we should remove
* state
*
* @returns Boolean indicating if the remove problem was currently active
* (that is, if it was at the head of the queue)
*/
PROT_BrowserView.prototype.removeProblemFromQueue_ = function(doc, browser) {
G_Debug(this, "Removing problem state for " + browser);
G_Assert(this, !!this.hasNonemptyProblemQueue_(browser),
"Browser has no problem state");
var problem = null;
// TODO Blech. Let's please have an abstraction here instead.
for (var i = 0; i < browser.PROT_problemState__.length; i++)
if (browser.PROT_problemState__[i].doc_ === doc) {
problem = browser.PROT_problemState__.splice(i, 1)[0];
break;
}
return problem;
}
/**
* Retrieve (but do not remove) the problem state for a particular
* problematic Document in this browser
*
* @param doc Reference to the problematic doc to get state for
*
* @param browser Reference to the browser from which to get state
*
* @returns Object encapsulating the state we stored, or null if none
*/
PROT_BrowserView.prototype.getProblem_ = function(doc, browser) {
if (!this.hasNonemptyProblemQueue_(browser))
return null;
// TODO Blech. Let's please have an abstraction here instead.
for (var i = 0; i < browser.PROT_problemState__.length; i++)
if (browser.PROT_problemState__[i].doc_ === doc)
return browser.PROT_problemState__[i];
return null;
}
/**
* Retrieve the problem state for the currently active problem Document
* in this browser
*
* @param browser Reference to the browser from which to get state
*
* @returns Object encapsulating the state we stored, or null if none
*/
PROT_BrowserView.prototype.getCurrentProblem_ = function(browser) {
return browser.PROT_problemState__[0];
}
/**
* Invoked by the controller when the user switches tabs away from a problem
* tab.
*
* @param browser Reference to the tab that was switched from
*/
PROT_BrowserView.prototype.problemBrowserUnselected = function(browser) {
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "Couldn't get state from browser");
problem.displayer_.browserUnselected();
}
/**
* Checks to see if the problem browser is selected, and if so,
* tell it it to show its warning.
*
* @param browser Reference to the browser we wish to check
*/
PROT_BrowserView.prototype.problemBrowserMaybeSelected = function(browser) {
var problem = this.getCurrentProblem_(browser);
if (this.tabWatcher_.getCurrentBrowser() === browser &&
problem &&
problem.displayer_.isActive())
this.problemBrowserSelected(browser);
}
/**
* Invoked by the controller when the user switches tabs to a problem tab
*
* @param browser Reference to the tab that was switched to
*/
PROT_BrowserView.prototype.problemBrowserSelected = function(browser) {
G_Debug(this, "Problem browser selected");
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "No state? But we're selected!");
problem.displayer_.browserSelected();
}
/**
* Invoked by the controller when the user accepts our warning. Passes
* the accept through to the message displayer, which knows what to do
* (it will be displayer-specific).
*
* @param browser Reference to the browser for which the user accepted
* our warning
*/
PROT_BrowserView.prototype.acceptAction = function(browser) {
var problem = this.getCurrentProblem_(browser);
// We run the action only after we're completely through processing
// this event. We do this because the action could cause state to be
// cleared (e.g., by navigating the problem document) that we need
// to finish processing the event.
new G_Alarm(BindToObject(problem.displayer_.acceptAction,
problem.displayer_),
0);
}
/**
* Invoked by the controller when the user declines our
* warning. Passes the decline through to the message displayer, which
* knows what to do (it will be displayer-specific).
*
* @param browser Reference to the browser for which the user declined
* our warning
*/
PROT_BrowserView.prototype.declineAction = function(browser) {
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "User declined but no state???");
// We run the action only after we're completely through processing
// this event. We do this because the action could cause state to be
// cleared (e.g., by navigating the problem document) that we need
// to finish processing the event.
new G_Alarm(BindToObject(problem.displayer_.declineAction,
problem.displayer_),
0);
}
/**
* The user wants to see the warning message. So let em! At some point when
* we have multiple types of warnings, we'll have to mediate them here.
*
* @param browser Reference to the browser that has the warning the user
* wants to see.
*/
PROT_BrowserView.prototype.explicitShow = function(browser) {
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "Explicit show on browser w/o problem state???");
problem.displayer_.explicitShow();
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This is our controller -- the thingy that listens to what the user
// is doing. There is one controller per browser window, and each has
// a BrowserView that manages information about problems within the
// window. The controller figures out when the browser might want to
// know about something, but the browser view figures out what exactly
// to do (and the BrowserView's displayer figures out how to do it).
//
// For example, the controller might notice that the user has switched
// to a tab that has something problematic in it. It would tell its
// BrowserView this, and the BrowserView would figure out whether it
// is appropriate to show a warning (e.g., perhaps the user previously
// dismissed the warning for that problem). If so, the BrowserView tells
// the displayer to show the warning. Anyhoo...
//
// TODO Could move all browser-related hide/show logic into the browser
// view. Need to think about this more.
/**
* Handles user actions, translating them into messages to the view
*
* @constructor
* @param win Reference to the Window (browser window context) we should
* attach to
* @param tabWatcher Reference to the TabbedBrowserWatcher object
* the controller should use to receive events about tabs.
* @param phishingWarden Reference to the PhishingWarden we should register
* our browserview with
*/
function PROT_Controller(win, tabWatcher, phishingWarden) {
this.debugZone = "controller";
this.win_ = win;
this.phishingWarden_ = phishingWarden;
// Use this to query preferences
this.prefs_ = new G_Preferences();
// Set us up to receive the events we want.
this.tabWatcher_ = tabWatcher;
this.onTabSwitchCallback_ = BindToObject(this.onTabSwitch, this);
this.tabWatcher_.registerListener("tabswitch",
this.onTabSwitchCallback_);
// Install our command controllers. These commands are issued from
// various places in our UI, including our preferences dialog, the
// warning dialog, etc.
var commandHandlers = {
"safebrowsing-show-warning" :
BindToObject(this.onUserShowWarning, this),
"safebrowsing-accept-warning" :
BindToObject(this.onUserAcceptWarning, this),
"safebrowsing-decline-warning" :
BindToObject(this.onUserDeclineWarning, this),
};
this.commandController_ = new PROT_CommandController(commandHandlers);
this.win_.controllers.appendController(this.commandController_);
// This guy embodies the logic of when to display warnings
// (displayers embody the how).
this.browserView_ = new PROT_BrowserView(this.tabWatcher_,
this.win_.document);
// We need to let the phishing warden know about this browser view so it
// can be given the opportunity to handle problem documents. We also need
// to let the warden know when this window and hence this browser view
// is going away.
this.phishingWarden_.addBrowserView(this.browserView_);
this.windowWatcher_ =
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
G_Debug(this, "Controller initialized.");
}
/**
* Invoked when the browser window is closing. Do some cleanup.
*/
PROT_Controller.prototype.shutdown = function(e) {
G_Debug(this, "Browser window closing. Shutting controller down.");
if (this.browserView_) {
this.phishingWarden_.removeBrowserView(this.browserView_);
}
if (this.commandController_) {
this.win_.controllers.removeController(this.commandController_);
this.commandController_ = null;
}
// No need to drain the browser view's problem queue explicitly; it will
// receive pagehides for all the browsers in its queues as they're torn
// down, and it will remove them.
this.browserView_ = null;
if (this.tabWatcher_) {
this.tabWatcher_.removeListener("tabswitch",
this.onTabSwitchCallback_);
this.tabWatcher_.shutdown();
}
this.win_.removeEventListener("unload", this.onShutdown_, false);
this.prefs_ = null;
this.windowWatcher_ = null;
G_Debug(this, "Controller shut down.");
}
/**
* The user clicked the urlbar icon; they want to see the warning message
* again.
*/
PROT_Controller.prototype.onUserShowWarning = function() {
var browser = this.tabWatcher_.getCurrentBrowser();
this.browserView_.explicitShow(browser);
}
/**
* Deal with a user accepting our warning.
*
* TODO the warning hide/display instructions here can probably be moved
* into the browserview in the future, given its knowledge of when the
* problem doc hides/shows.
*/
PROT_Controller.prototype.onUserAcceptWarning = function() {
G_Debug(this, "User accepted warning.");
var browser = this.tabWatcher_.getCurrentBrowser();
G_Assert(this, !!browser, "Couldn't get current browser?!?");
G_Assert(this, this.browserView_.hasProblem(browser),
"User accept fired, but browser doesn't have warning showing?!?");
this.browserView_.acceptAction(browser);
this.browserView_.problemResolved(browser);
}
/**
* Deal with a user declining our warning.
*
* TODO the warning hide/display instructions here can probably be moved
* into the browserview in the future, given its knowledge of when the
* problem doc hides/shows.
*/
PROT_Controller.prototype.onUserDeclineWarning = function() {
G_Debug(this, "User declined warning.");
var browser = this.tabWatcher_.getCurrentBrowser();
G_Assert(this, this.browserView_.hasProblem(browser),
"User decline fired, but browser doesn't have warning showing?!?");
this.browserView_.declineAction(browser);
// We don't call problemResolved() here because all declining does it
// hide the message; we still have the urlbar icon showing, giving
// the user the ability to bring the warning message back up if they
// so desire.
}
/**
* Notice tab switches, and display or hide warnings as appropriate.
*
* TODO this logic can probably move into the browser view at some
* point. But one thing at a time.
*/
PROT_Controller.prototype.onTabSwitch = function(e) {
if (this.browserView_.hasProblem(e.fromBrowser))
this.browserView_.problemBrowserUnselected(e.fromBrowser);
if (this.browserView_.hasProblem(e.toBrowser))
this.browserView_.problemBrowserSelected(e.toBrowser);
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Some misc command-related plumbing used by the controller.
/**
* A tiny wrapper class for super-simple command handlers.
*
* @param commandHandlerMap An object containing name/value pairs where
* the name is command name (string) and value
* is the function to execute for that command
*/
function PROT_CommandController(commandHandlerMap) {
this.debugZone = "commandhandler";
this.cmds_ = commandHandlerMap;
}
/**
* @param cmd Command to query support for
* @returns Boolean indicating if this controller supports cmd
*/
PROT_CommandController.prototype.supportsCommand = function(cmd) {
return (cmd in this.cmds_);
}
/**
* Trivial implementation
*
* @param cmd Command to query status of
*/
PROT_CommandController.prototype.isCommandEnabled = function(cmd) {
return true;
}
/**
* Execute a command
*
* @param cmd Command to execute
*/
PROT_CommandController.prototype.doCommand = function(cmd) {
return this.cmds_[cmd]();
}
/**
* Trivial implementation
*/
PROT_CommandController.prototype.onEvent = function(cmd) { }
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
* J. Paul Reed <preed@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A class that encapsulates data provider specific values. The
// root of the provider pref tree is browser.safebrowsing.provider.
// followed by a number, followed by specific properties. The properties
// that a data provider can supply are:
//
// name: The name of the provider
// lookupURL: The URL to send requests to in enhanced mode
// keyURL: Before we send URLs in enhanced mode, we need to encrypt them
// reportURL: When shown a warning bubble, we send back the user decision
// (get me out of here/ignore warning) to this URL (strip cookies
// first). This is optional.
// reportGenericURL: HTML page for general user feedback
// reportPhishURL: HTML page for notifying the provider of a new phishing page
// reportErrorURL: HTML page for notifying the provider of a false positive
const kDataProviderIdPref = 'browser.safebrowsing.dataProvider';
const kProviderBasePref = 'browser.safebrowsing.provider.';
//@line 60 "/cygdrive/K/tinderbuild/src/flock/mozilla/browser/components/safebrowsing/src/../content/globalstore.js"
const MOZ_OFFICIAL_BUILD = false;
//@line 62 "/cygdrive/K/tinderbuild/src/flock/mozilla/browser/components/safebrowsing/src/../content/globalstore.js"
const MOZ_PARAM_LOCALE = /\{moz:locale\}/g;
const MOZ_PARAM_CLIENT = /\{moz:client\}/g;
const MOZ_PARAM_BUILDID = /\{moz:buildid\}/g;
const MOZ_PARAM_VERSION = /\{moz:version\}/g;
/**
* Information regarding the data provider.
*/
function PROT_DataProvider() {
this.prefs_ = new G_Preferences();
this.loadDataProviderPrefs_();
// Watch for changes in the data provider and update accordingly.
this.prefs_.addObserver(kDataProviderIdPref,
BindToObject(this.loadDataProviderPrefs_, this));
// Watch for when anti-phishing is toggled on or off.
this.prefs_.addObserver(kPhishWardenEnabledPref,
BindToObject(this.loadDataProviderPrefs_, this));
// Watch for when remote lookups are toggled on or off.
this.prefs_.addObserver(kPhishWardenRemoteLookups,
BindToObject(this.loadDataProviderPrefs_, this));
}
/**
* Populate all the provider variables. We also call this when whenever
* the provider id changes.
*/
PROT_DataProvider.prototype.loadDataProviderPrefs_ = function() {
// Currently, there's no UI for changing local list provider so we
// hard code the value for provider 0.
this.updateURL_ = this.getUrlPref_(
'browser.safebrowsing.provider.0.updateURL');
var id = this.prefs_.getPref(kDataProviderIdPref, null);
// default to 0
if (null == id)
id = 0;
var basePref = kProviderBasePref + id + '.';
this.name_ = this.prefs_.getPref(basePref + "name", "");
// Urls used to get data from a provider
this.lookupURL_ = this.getUrlPref_(basePref + "lookupURL");
this.keyURL_ = this.getUrlPref_(basePref + "keyURL");
this.reportURL_ = this.getUrlPref_(basePref + "reportURL");
// Urls to HTML report pages
this.reportGenericURL_ = this.getUrlPref_(basePref + "reportGenericURL");
this.reportErrorURL_ = this.getUrlPref_(basePref + "reportErrorURL");
this.reportPhishURL_ = this.getUrlPref_(basePref + "reportPhishURL");
// Propogate the changes to the list-manager.
this.updateListManager_();
}
/**
* The list manager needs urls to operate. It needs a url to know where the
* table updates are, and it needs a url for decrypting enchash style tables.
*/
PROT_DataProvider.prototype.updateListManager_ = function() {
var listManager = Cc["@mozilla.org/url-classifier/listmanager;1"]
.getService(Ci.nsIUrlListManager);
// If we add support for changing local data providers, we need to add a
// pref observer that sets the update url accordingly.
listManager.setUpdateUrl(this.getUpdateURL());
// setKeyUrl has the side effect of fetching a key from the server.
// This shouldn't happen if anti-phishing is disabled or we're in local
// list mode, so we need to check for that.
var isEnabled = this.prefs_.getPref(kPhishWardenEnabledPref, false);
var remoteLookups = this.prefs_.getPref(kPhishWardenRemoteLookups, false);
if (isEnabled && remoteLookups) {
listManager.setKeyUrl(this.getKeyURL());
} else {
// Clear the key to stop updates.
listManager.setKeyUrl("");
}
}
/**
* Lookup the value of a URL from prefs file and do parameter substitution.
*/
PROT_DataProvider.prototype.getUrlPref_ = function(prefName) {
var url = this.prefs_.getPref(prefName);
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULAppInfo);
var mozClientStr = MOZ_OFFICIAL_BUILD ? 'navclient-auto-ffox' : appInfo.name;
// Parameter substitution
url = url.replace(MOZ_PARAM_LOCALE, this.getLocale_());
url = url.replace(MOZ_PARAM_CLIENT, mozClientStr);
url = url.replace(MOZ_PARAM_BUILDID, appInfo.appBuildID);
url = url.replace(MOZ_PARAM_VERSION, appInfo.version);
return url;
}
/**
* @return String the browser locale (similar code is in nsSearchService.js)
*/
PROT_DataProvider.prototype.getLocale_ = function() {
const localePref = "general.useragent.locale";
var locale = this.getLocalizedPref_(localePref);
if (locale)
return locale;
// Not localized
var prefs = new G_Preferences();
return prefs.getPref(localePref, "");
}
/**
* @return String name of the localized pref, null if none exists.
*/
PROT_DataProvider.prototype.getLocalizedPref_ = function(aPrefName) {
// G_Preferences doesn't know about complex values, so we use the
// xpcom object directly.
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
try {
return prefs.getComplexValue(aPrefName, Ci.nsIPrefLocalizedString).data;
} catch (ex) {
}
return "";
}
//////////////////////////////////////////////////////////////////////////////
// Getters for the remote provider pref values mentioned above.
PROT_DataProvider.prototype.getName = function() {
return this.name_;
}
PROT_DataProvider.prototype.getUpdateURL = function() {
return this.updateURL_;
}
PROT_DataProvider.prototype.getLookupURL = function() {
return this.lookupURL_;
}
PROT_DataProvider.prototype.getKeyURL = function() {
return this.keyURL_;
}
PROT_DataProvider.prototype.getReportURL = function() {
return this.reportURL_;
}
PROT_DataProvider.prototype.getReportGenericURL = function() {
return this.reportGenericURL_;
}
PROT_DataProvider.prototype.getReportErrorURL = function() {
return this.reportErrorURL_;
}
PROT_DataProvider.prototype.getReportPhishURL = function() {
return this.reportPhishURL_;
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Niels Provos <niels@google.com> (original author)d
* Fritz Schneider <fritz@google.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A warden that knows how to register lists with a listmanager and keep them
// updated if necessary. The ListWarden also provides a simple interface to
// check if a URL is evil or not. Specialized wardens like the PhishingWarden
// inherit from it.
//
// Classes that inherit from ListWarden are responsible for calling
// enableTableUpdates or disableTableUpdates. This usually entails
// registering prefObservers and calling enable or disable in the base
// class as appropriate.
//
/**
* Abtracts the checking of user/browser actions for signs of
* phishing.
*
* @constructor
*/
function PROT_ListWarden() {
this.debugZone = "listwarden";
var listManager = Cc["@mozilla.org/url-classifier/listmanager;1"]
.getService(Ci.nsIUrlListManager);
this.listManager_ = listManager;
// Once we register tables, their respective names will be listed here.
this.blackTables_ = [];
this.whiteTables_ = [];
}
PROT_ListWarden.IN_BLACKLIST = 0
PROT_ListWarden.IN_WHITELIST = 1
PROT_ListWarden.NOT_FOUND = 2
/**
* Tell the ListManger to keep all of our tables updated
*/
PROT_ListWarden.prototype.enableBlacklistTableUpdates = function() {
for (var i = 0; i < this.blackTables_.length; ++i) {
this.listManager_.enableUpdate(this.blackTables_[i]);
}
}
/**
* Tell the ListManager to stop updating our tables
*/
PROT_ListWarden.prototype.disableBlacklistTableUpdates = function() {
for (var i = 0; i < this.blackTables_.length; ++i) {
this.listManager_.disableUpdate(this.blackTables_[i]);
}
}
/**
* Tell the ListManager to update whitelist tables. They may be enabled even
* when other updates aren't, for performance reasons.
*/
PROT_ListWarden.prototype.enableWhitelistTableUpdates = function() {
for (var i = 0; i < this.whiteTables_.length; ++i) {
this.listManager_.enableUpdate(this.whiteTables_[i]);
}
}
/**
* Tell the ListManager to stop updating whitelist tables.
*/
PROT_ListWarden.prototype.disableWhitelistTableUpdates = function() {
for (var i = 0; i < this.whiteTables_.length; ++i) {
this.listManager_.disableUpdate(this.whiteTables_[i]);
}
}
/**
* Register a new black list table with the list manager
* @param tableName - name of the table to register
* @returns true if the table could be registered, false otherwise
*/
PROT_ListWarden.prototype.registerBlackTable = function(tableName) {
var result = this.listManager_.registerTable(tableName, false);
if (result) {
this.blackTables_.push(tableName);
}
return result;
}
/**
* Register a new white list table with the list manager
* @param tableName - name of the table to register
* @returns true if the table could be registered, false otherwise
*/
PROT_ListWarden.prototype.registerWhiteTable = function(tableName) {
var result = this.listManager_.registerTable(tableName, false);
if (result) {
this.whiteTables_.push(tableName);
}
return result;
}
/**
* Method that looks up a url on the whitelist.
*
* @param url The URL to check
* @param callback Function with a single param:
* PROT_ListWarden.IN_BLACKLIST, PROT_ListWarden.IN_WHITELIST,
* or PROT_ListWarden.NOT_FOUND
*/
PROT_ListWarden.prototype.isWhiteURL = function(url, callback) {
(new MultiTableQuerier(url,
this.whiteTables_,
[] /* no blacklists */,
callback)).run();
}
/**
* Method that looks up a url in both the white and black lists.
*
* If there is conflict, the white list has precedence over the black list.
*
* This is tricky because all database queries are asynchronous. So we need
* to chain together our checks against white and black tables. We use
* MultiTableQuerier (see below) to manage this.
*
* @param url URL to look up
* @param callback Function with a single param:
* PROT_ListWarden.IN_BLACKLIST, PROT_ListWarden.IN_WHITELIST,
* or PROT_ListWarden.NOT_FOUND
*/
PROT_ListWarden.prototype.isEvilURL = function(url, callback) {
(new MultiTableQuerier(url,
this.whiteTables_,
this.blackTables_,
callback)).run();
}
/**
* This class helps us query multiple tables even though each table check
* is asynchronous. It provides callbacks for each listManager lookup
* and decides whether we need to continue querying or not. After
* instantiating the method, use run() to invoke.
*
* @param url String The url to check
* @param whiteTables Array of strings with each white table name
* @param blackTables Array of strings with each black table name
* @param callback Function to call with result
* PROT_ListWarden.IN_BLACKLIST, PROT_ListWarden.IN_WHITELIST,
* or PROT_ListWarden.NOT_FOUND
*/
function MultiTableQuerier(url, whiteTables, blackTables, callback) {
this.debugZone = "multitablequerier";
this.url_ = url;
this.whiteTables_ = whiteTables;
this.blackTables_ = blackTables;
this.whiteIdx_ = 0;
this.blackIdx_ = 0;
this.callback_ = callback;
this.listManager_ = Cc["@mozilla.org/url-classifier/listmanager;1"]
.getService(Ci.nsIUrlListManager);
}
/**
* We first query the white tables in succession. If any contain
* the url, we stop. If none contain the url, we query the black tables
* in succession. If any contain the url, we call callback and
* stop. If none of the black tables contain the url, then we just stop
* (i.e., it's not black url).
*/
MultiTableQuerier.prototype.run = function() {
var whiteTable = this.whiteTables_[this.whiteIdx_];
var blackTable = this.blackTables_[this.blackIdx_];
if (whiteTable) {
//G_Debug(this, "Looking in whitetable: " + whiteTable);
++this.whiteIdx_;
this.listManager_.safeExists(whiteTable, this.url_,
BindToObject(this.whiteTableCallback_,
this));
} else if (blackTable) {
//G_Debug(this, "Looking in blacktable: " + blackTable);
++this.blackIdx_;
this.listManager_.safeExists(blackTable, this.url_,
BindToObject(this.blackTableCallback_,
this));
} else {
// No tables left to check, so we quit.
G_Debug(this, "Not found in any tables: " + this.url_);
this.callback_(PROT_ListWarden.NOT_FOUND);
// Break circular ref to callback.
this.callback_ = null;
this.listManager_ = null;
}
}
/**
* After checking a white table, we return here. If the url is found,
* we can stop. Otherwise, we call run again.
*/
MultiTableQuerier.prototype.whiteTableCallback_ = function(isFound) {
//G_Debug(this, "whiteTableCallback_: " + isFound);
if (!isFound)
this.run();
else {
G_Debug(this, "Found in whitelist: " + this.url_)
this.callback_(PROT_ListWarden.IN_WHITELIST);
// Break circular ref to callback.
this.callback_ = null;
this.listManager_ = null;
}
}
/**
* After checking a black table, we return here. If the url is found,
* we can call the callback and stop. Otherwise, we call run again.
*/
MultiTableQuerier.prototype.blackTableCallback_ = function(isFound) {
//G_Debug(this, "blackTableCallback_: " + isFound);
if (!isFound) {
this.run();
} else {
// In the blacklist, must be an evil url.
G_Debug(this, "Found in blacklist: " + this.url_)
this.callback_(PROT_ListWarden.IN_BLACKLIST);
// Break circular ref to callback.
this.callback_ = null;
this.listManager_ = null;
}
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Implementation of the warning message we show users when we
// notice navigation to a phishing page after it has loaded. The
// browser view encapsulates all the hide/show logic, so the displayer
// doesn't need to know when to display itself, only how.
//
// Displayers implement the following interface:
//
// start() -- fired to initialize the displayer (to make it active). When
// called, this displayer starts listening for and responding to
// events. At most one displayer per tab should be active at a
// time, and start() should be called at most once.
// declineAction() -- fired when the user declines the warning.
// acceptAction() -- fired when the user declines the warning
// explicitShow() -- fired when the user wants to see the warning again
// browserSelected() -- the browser is the top tab
// browserUnselected() -- the browser is no long the top tab
// done() -- clean up. May be called once (even if the displayer wasn't
// activated).
//
// At the moment, all displayers share access to the same xul in
// safebrowsing-overlay.xul. Hence the need for at most one displayer
// to be active per tab at a time.
/**
* Factory that knows how to create a displayer appropriate to the
* user's platform. We use a clunky canvas-based displayer for all
* platforms until such time as we can overlay semi-transparent
* areas over browser content.
*
* See the base object for a description of the constructor args
*
* @constructor
*/
function PROT_PhishMsgDisplayer(msgDesc, browser, doc, url) {
// TODO: Change this to return a PhishMsgDisplayerTransp on windows
// (and maybe other platforms) when Firefox 2.0 hits.
return new PROT_PhishMsgDisplayerCanvas(msgDesc, browser, doc, url);
}
/**
* Base class that implements most of the plumbing required to hide
* and show a phishing warning. Subclasses implement the actual
* showMessage and hideMessage methods.
*
* This class is not meant to be instantiated directly.
*
* @param msgDesc String describing the kind of warning this is
* @param browser Reference to the browser over which we display the msg
* @param doc Reference to the document in which browser is found
* @param url String containing url of the problem document
* @constructor
*/
function PROT_PhishMsgDisplayerBase(msgDesc, browser, doc, url) {
this.debugZone = "phishdisplayer";
this.msgDesc_ = msgDesc; // currently unused
this.browser_ = browser;
this.doc_ = doc;
this.url_ = url;
// We'll need to manipulate the XUL in safebrowsing-overlay.xul
this.messageId_ = "safebrowsing-palm-message";
this.messageTailId_ = "safebrowsing-palm-message-tail-container";
this.messageContentId_ = "safebrowsing-palm-message-content";
this.extendedMessageId_ = "safebrowsing-palm-extended-message";
this.showmoreLinkId_ = "safebrowsing-palm-showmore-link";
this.faqLinkId_ = "safebrowsing-palm-faq-link";
this.urlbarIconId_ = "safebrowsing-urlbar-icon";
this.refElementId_ = this.urlbarIconId_;
// We use this to report user actions to the server
this.reporter_ = new PROT_Reporter();
// The active UI elements in our warning send these commands; bind them
// to their handlers but don't register the commands until we start
// (because another displayer might be active)
this.commandHandlers_ = {
"safebrowsing-palm-showmore":
BindToObject(this.showMore_, this),
};
this.windowWatcher_ =
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
}
/**
* @returns The default background color of the browser
*/
PROT_PhishMsgDisplayerBase.prototype.getBackgroundColor_ = function() {
var pref = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
return pref.getCharPref("browser.display.background_color");
}
/**
* Fired when the user declines our warning. Report it!
*/
PROT_PhishMsgDisplayerBase.prototype.declineAction = function() {
G_Debug(this, "User declined warning.");
G_Assert(this, this.started_, "Decline on a non-active displayer?");
this.reporter_.report("phishdecline", this.url_);
this.messageShouldShow_ = false;
if (this.messageShowing_)
this.hideMessage_();
}
/**
* Fired when the user accepts our warning
*/
PROT_PhishMsgDisplayerBase.prototype.acceptAction = function() {
G_Assert(this, this.started_, "Accept on an unstarted displayer?");
G_Assert(this, this.done_, "Accept on a finished displayer?");
G_Debug(this, "User accepted warning.");
this.reporter_.report("phishaccept", this.url_);
var url = this.getMeOutOfHereUrl_();
this.browser_.loadURI(url);
}
/**
* Get the url for "Get me out of here." This is the browser's default home
* page, or, about:blank.
* @return String url
*/
PROT_PhishMsgDisplayerBase.prototype.getMeOutOfHereUrl_ = function() {
// Try to get their homepage from prefs.
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService).getDefaultBranch(null);
var url = "about:blank";
try {
url = prefs.getComplexValue("browser.startup.homepage",
Ci.nsIPrefLocalizedString).data;
// If url is a pipe-delimited set of pages, just take the first one.
// This will need to change once bug 221445 is fixed.
if (url.indexOf("|") != -1)
url = url.split("|")[0];
} catch(e) {
G_Debug(this, "Couldn't get homepage pref: " + e);
}
return url;
}
/**
* Invoked when the browser is resized
*/
PROT_PhishMsgDisplayerBase.prototype.onBrowserResized_ = function(event) {
G_Debug(this, "Got resize for " + event.target);
if (this.messageShowing_) {
this.hideMessage_();
this.showMessage_();
}
}
/**
* Invoked by the browser view when our browser is switched to
*/
PROT_PhishMsgDisplayerBase.prototype.browserSelected = function() {
G_Assert(this, this.started_, "Displayer selected before being started???");
// If messageshowing hasn't been set, then this is the first time this
// problematic browser tab has been on top, so do our setup and show
// the warning.
if (this.messageShowing_ === undefined) {
this.messageShouldShow_ = true;
}
this.hideLockIcon_(); // Comes back when we are unselected or unloaded
this.addWarningInUrlbar_(); // Goes away when we are unselected or unloaded
// messageShouldShow might be false if the user dismissed the warning,
// switched tabs, and then switched back. We're still active, but don't
// want to show the warning again. The user can cause it to show by
// clicking our icon in the urlbar.
if (this.messageShouldShow_)
this.showMessage_();
}
/**
* Invoked to display the warning message explicitly, for example if the user
* clicked the url warning icon.
*/
PROT_PhishMsgDisplayerBase.prototype.explicitShow = function() {
this.messageShouldShow_ = true;
if (!this.messageShowing_)
this.showMessage_();
}
/**
* Invoked by the browser view when our browser is switched away from
*/
PROT_PhishMsgDisplayerBase.prototype.browserUnselected = function() {
this.removeWarningInUrlbar_();
this.unhideLockIcon_();
if (this.messageShowing_)
this.hideMessage_();
}
/**
* Invoked to make this displayer active. The displayer will now start
* responding to notifications such as commands and resize events. We
* can't do this in the constructor because there might be many
* displayers instantiated waiting in the problem queue for a particular
* browser (e.g., a page has multiple framed problem pages), and we
* don't want them all responding to commands!
*
* Invoked zero (the page we're a warning for was nav'd away from
* before it reaches the head of the problem queue) or one (we're
* displaying this warning) times by the browser view.
*/
PROT_PhishMsgDisplayerBase.prototype.start = function() {
G_Assert(this, this.started_ == undefined, "Displayer started twice?");
this.started_ = true;
this.commandController_ = new PROT_CommandController(this.commandHandlers_);
this.doc_.defaultView.controllers.appendController(this.commandController_);
// Add an event listener for when the browser resizes (e.g., user
// shows/hides the sidebar).
this.resizeHandler_ = BindToObject(this.onBrowserResized_, this);
this.browser_.addEventListener("resize",
this.resizeHandler_,
false);
}
/**
* @returns Boolean indicating whether this displayer is currently
* active
*/
PROT_PhishMsgDisplayerBase.prototype.isActive = function() {
return !!this.started_;
}
/**
* Invoked by the browser view to clean up after the user is done
* interacting with the message. Should be called once by the browser
* view.
*/
PROT_PhishMsgDisplayerBase.prototype.done = function() {
G_Assert(this, !this.done_, "Called done more than once?");
this.done_ = true;
// If the Document we're showing the warning for was nav'd away from
// before we had a chance to get started, we have nothing to do.
if (this.started_) {
// If we were started, we must be the current problem, so these things
// must be showing
this.removeWarningInUrlbar_();
this.unhideLockIcon_();
// Could be though that they've closed the warning dialog
if (this.messageShowing_)
this.hideMessage_();
if (this.resizeHandler_) {
this.browser_.removeEventListener("resize",
this.resizeHandler_,
false);
this.resizeHandler_ = null;
}
var win = this.doc_.defaultView;
win.controllers.removeController(this.commandController_);
this.commandController_ = null;
}
}
/**
* Helper function to remove a substring from inside a string.
*
* @param orig String to remove substring from
*
* @param toRemove String to remove (if it is present)
*
* @returns String with the substring removed
*/
PROT_PhishMsgDisplayerBase.prototype.removeIfExists_ = function(orig,
toRemove) {
var pos = orig.indexOf(toRemove);
if (pos != -1)
orig = orig.substring(0, pos) + orig.substring(pos + toRemove.length);
return orig;
}
/**
* We don't want to confuse users if they land on a phishy page that uses
* SSL, so ensure that the lock icon never shows when we're showing our
* warning.
*/
PROT_PhishMsgDisplayerBase.prototype.hideLockIcon_ = function() {
var lockIcon = this.doc_.getElementById("lock-icon");
if (!lockIcon)
return;
lockIcon.hidden = true;
}
/**
* Ensure they can see it after our warning is finished.
*/
PROT_PhishMsgDisplayerBase.prototype.unhideLockIcon_ = function() {
var lockIcon = this.doc_.getElementById("lock-icon");
if (!lockIcon)
return;
lockIcon.hidden = false;
}
/**
* This method makes our warning icon visible in the location bar. It will
* be removed only when the problematic document is navigated awy from
* (i.e., when done() is called), and not when the warning is dismissed.
*/
PROT_PhishMsgDisplayerBase.prototype.addWarningInUrlbar_ = function() {
var urlbarIcon = this.doc_.getElementById(this.urlbarIconId_);
if (!urlbarIcon)
return;
urlbarIcon.setAttribute('level', 'warn');
}
/**
* Hides our urlbar icon
*/
PROT_PhishMsgDisplayerBase.prototype.removeWarningInUrlbar_ = function() {
var urlbarIcon = this.doc_.getElementById(this.urlbarIconId_);
if (!urlbarIcon)
return;
urlbarIcon.setAttribute('level', 'safe');
}
/**
* VIRTUAL -- Displays the warning message
*/
PROT_PhishMsgDisplayerBase.prototype.showMessage_ = function() { };
/**
* VIRTUAL -- Hide the warning message from the user.
*/
PROT_PhishMsgDisplayerBase.prototype.hideMessage_ = function() { };
/**
* Reposition the message relative to refElement in the parent window
*
* @param message Reference to the element to position
* @param tail Reference to the message tail
* @param refElement Reference to element relative to which we position
* ourselves
*/
PROT_PhishMsgDisplayerBase.prototype.adjustLocation_ = function(message,
tail,
refElement) {
var refX = refElement.boxObject.x;
var refY = refElement.boxObject.y;
var refHeight = refElement.boxObject.height;
var refWidth = refElement.boxObject.width;
G_Debug(this, "Ref element is at [window-relative] (" + refX + ", " +
refY + ")");
var pixelsIntoRefY = -2;
var tailY = refY + refHeight - pixelsIntoRefY;
var tailPixelsLeftOfRefX = tail.boxObject.width;
var tailPixelsIntoRefX = Math.round(refWidth / 2);
var tailX = refX - tailPixelsLeftOfRefX + tailPixelsIntoRefX;
// Move message up a couple pixels so the tail overlaps it.
var messageY = tailY + tail.boxObject.height - 2;
var messagePixelsLeftOfRefX = 375;
var messageX = refX - messagePixelsLeftOfRefX;
G_Debug(this, "Message is at [window-relative] (" + messageX + ", " +
messageY + ")");
G_Debug(this, "Tail is at [window-relative] (" + tailX + ", " +
tailY + ")");
if (messageX < 0) {
// We're hanging off the left edge, switch to floating mode
tail.style.display = "none";
this.adjustLocationFloating_(message);
return;
}
tail.style.top = tailY + "px";
tail.style.left = tailX + "px";
message.style.top = messageY + "px";
message.style.left = messageX + "px";
this.maybeAddScrollbars_();
}
/**
* Position the warning bubble with no reference element. In this case we
* just center the warning bubble at the top of the users window.
* @param message XULElement message bubble XUL container.
*/
PROT_PhishMsgDisplayerBase.prototype.adjustLocationFloating_ = function(message) {
// Compute X offset
var browserX = this.browser_.boxObject.x;
var browserXCenter = browserX + this.browser_.boxObject.width / 2;
var messageX = browserXCenter - (message.boxObject.width / 2);
// Compute Y offset (top of the browser window)
var messageY = this.browser_.boxObject.y;
// Position message
message.style.top = messageY + "px";
message.style.left = messageX + "px";
this.maybeAddScrollbars_();
}
/**
* Add a vertical scrollbar if we're falling out of the browser window.
*/
PROT_PhishMsgDisplayerBase.prototype.maybeAddScrollbars_ = function() {
var message = this.doc_.getElementById(this.messageId_);
var content = this.doc_.getElementById(this.messageContentId_);
var bottom = content.boxObject.y + content.boxObject.height;
var maxY = this.doc_.defaultView.innerHeight;
G_Debug(this, "bottom: " + bottom + ", maxY: " + maxY
+ ", new height: " + (maxY - content.boxObject.y));
if (bottom > maxY) {
var newHeight = maxY - content.boxObject.y;
if (newHeight < 1)
newHeight = 1;
content.style.height = newHeight + "px";
content.style.overflow = "auto";
}
}
/**
* Show the extended warning message
*/
PROT_PhishMsgDisplayerBase.prototype.showMore_ = function() {
this.doc_.getElementById(this.extendedMessageId_).hidden = false;
this.doc_.getElementById(this.showmoreLinkId_).style.display = "none";
// set FAQ URL
var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
.getService(Components.interfaces.nsIURLFormatter);
var faqURL = formatter.formatURLPref("browser.safebrowsing.warning.infoURL");
var labelEl = this.doc_.getElementById(this.faqLinkId_);
labelEl.setAttribute("href", faqURL);
this.maybeAddScrollbars_();
}
/**
* The user clicked on one of the links in the buble. Display the
* corresponding page in a new window with all the chrome enabled.
*
* @param url The URL to display in a new window
*/
PROT_PhishMsgDisplayerBase.prototype.showURL_ = function(url) {
this.windowWatcher_.openWindow(this.windowWatcher_.activeWindow,
url,
"_blank",
null,
null);
}
/**
* If the warning bubble came up in error, this url goes to a form
* to notify the data provider.
* @return url String
*/
PROT_PhishMsgDisplayerBase.prototype.getReportErrorURL_ = function() {
var badUrl = this.url_;
var url = gDataProvider.getReportErrorURL();
url += "&url=" + encodeURIComponent(badUrl);
return url;
}
/**
* URL for the user to report back to us. This is to provide the user
* with an action after being warned.
*/
PROT_PhishMsgDisplayerBase.prototype.getReportGenericURL_ = function() {
var badUrl = this.url_;
var url = gDataProvider.getReportGenericURL();
url += "&url=" + encodeURIComponent(badUrl);
return url;
}
/**
* A specific implementation of the dislpayer using a canvas. This
* class is meant for use on platforms that don't support transparent
* elements over browser content (currently: all platforms).
*
* The main ugliness is the fact that we're hiding the content area and
* painting the page to canvas. As a result, we must periodically
* re-paint the canvas to reflect updates to the page. Otherwise if
* the page was half-loaded when we showed our warning, it would
* stay that way even though the page actually finished loading.
*
* See base constructor for full details of constructor args.
*
* @constructor
*/
function PROT_PhishMsgDisplayerCanvas(msgDesc, browser, doc, url) {
PROT_PhishMsgDisplayerBase.call(this, msgDesc, browser, doc, url);
this.dimAreaId_ = "safebrowsing-dim-area-canvas";
this.pageCanvasId_ = "safebrowsing-page-canvas";
this.xhtmlNS_ = "http://www.w3.org/1999/xhtml"; // we create html:canvas
}
PROT_PhishMsgDisplayerCanvas.inherits(PROT_PhishMsgDisplayerBase);
/**
* Displays the warning message. First we make sure the overlay is loaded
* then call showMessageAfterOverlay_.
*/
PROT_PhishMsgDisplayerCanvas.prototype.showMessage_ = function() {
G_Debug(this, "Showing message.");
// Load the overlay if we haven't already.
var dimmer = this.doc_.getElementById('safebrowsing-dim-area-canvas');
if (!dimmer) {
var onOverlayMerged = BindToObject(this.showMessageAfterOverlay_,
this);
var observer = new G_ObserverWrapper("xul-overlay-merged",
onOverlayMerged);
this.doc_.loadOverlay(
"chrome://browser/content/safebrowsing/warning-overlay.xul",
observer);
} else {
// The overlay is already loaded so we go ahead and call
// showMessageAfterOverlay_.
this.showMessageAfterOverlay_();
}
}
/**
* This does the actual work of showing the warning message.
*/
PROT_PhishMsgDisplayerCanvas.prototype.showMessageAfterOverlay_ = function() {
this.messageShowing_ = true;
// Position the canvas overlay. Order here is significant, but don't ask me
// why for some of these. You need to:
// 1. get browser dimensions
// 2. add canvas to the document
// 3. unhide the dimmer (gray out overlay)
// 4. display to the canvas
// 5. unhide the warning message
// 6. update link targets in warning message
// 7. focus the warning message
// (1)
var w = this.browser_.boxObject.width;
G_Debug(this, "browser w=" + w);
var h = this.browser_.boxObject.height;
G_Debug(this, "browser h=" + h);
var x = this.browser_.boxObject.x;
G_Debug(this, "browser x=" + w);
var y = this.browser_.boxObject.y;
G_Debug(this, "browser y=" + h);
var win = this.browser_.contentWindow;
var scrollX = win.scrollX;
G_Debug(this, "win scrollx=" + scrollX);
var scrollY = win.scrollY;
G_Debug(this, "win scrolly=" + scrollY);
// (2)
// We add the canvas dynamically and remove it when we're done because
// leaving it hanging around consumes a lot of memory.
var pageCanvas = this.doc_.createElementNS(this.xhtmlNS_, "html:canvas");
pageCanvas.id = this.pageCanvasId_;
pageCanvas.style.left = x + 'px';
pageCanvas.style.top = y + 'px';
var dimarea = this.doc_.getElementById(this.dimAreaId_);
this.doc_.getElementById('main-window').insertBefore(pageCanvas,
dimarea);
// (3)
dimarea.style.left = x + 'px';
dimarea.style.top = y + 'px';
dimarea.style.width = w + 'px';
dimarea.style.height = h + 'px';
dimarea.hidden = false;
// (4)
pageCanvas.setAttribute("width", w);
pageCanvas.setAttribute("height", h);
var bgcolor = this.getBackgroundColor_();
var cx = pageCanvas.getContext("2d");
cx.drawWindow(win, scrollX, scrollY, w, h, bgcolor);
// Now repaint the window every so often in case the content hasn't fully
// loaded at this point.
var debZone = this.debugZone;
function repaint() {
G_Debug(debZone, "Repainting canvas...");
cx.drawWindow(win, scrollX, scrollY, w, h, bgcolor);
};
this.repainter_ = new PROT_PhishMsgCanvasRepainter(repaint);
// (5)
this.showAndPositionWarning_();
// (6)
var link = this.doc_.getElementById('safebrowsing-palm-falsepositive-link');
link.href = this.getReportErrorURL_();
// (7)
this.doc_.getElementById(this.messageContentId_).focus();
}
/**
* Show and position the warning message. We position the waring message
* relative to the icon in the url bar, but if the element doesn't exist,
* (e.g., the user remove the url bar from her/his chrome), we anchor at the
* top of the window.
*/
PROT_PhishMsgDisplayerCanvas.prototype.showAndPositionWarning_ = function() {
var refElement = this.doc_.getElementById(this.refElementId_);
var message = this.doc_.getElementById(this.messageId_);
var tail = this.doc_.getElementById(this.messageTailId_);
message.hidden = false;
message.style.display = "block";
// Determine if the refElement is visible.
if (this.isVisibleElement_(refElement)) {
// Show tail and position warning relative to refElement.
tail.hidden = false;
tail.style.display = "block";
this.adjustLocation_(message, tail, refElement);
} else {
// No ref element, position in the top center of window.
tail.hidden = true;
tail.style.display = "none";
this.adjustLocationFloating_(message);
}
}
/**
* @return Boolean true if elt is a visible XUL element.
*/
PROT_PhishMsgDisplayerCanvas.prototype.isVisibleElement_ = function(elt) {
if (!elt)
return false;
// If it's on a collapsed/hidden toolbar, the x position is set to 0.
if (elt.boxObject.x == 0)
return false;
return true;
}
/**
* Hide the warning message from the user.
*/
PROT_PhishMsgDisplayerCanvas.prototype.hideMessage_ = function() {
G_Debug(this, "Hiding phishing warning.");
G_Assert(this, this.messageShowing_, "Hide message called but not showing?");
this.messageShowing_ = false;
this.repainter_.cancel();
this.repainter_ = null;
// Hide the warning popup.
var message = this.doc_.getElementById(this.messageId_);
message.hidden = true;
message.style.display = "none";
var content = this.doc_.getElementById(this.messageContentId_);
content.style.height = "";
content.style.overflow = "";
var tail = this.doc_.getElementById(this.messageTailId_);
tail.hidden = true;
tail.style.display = "none";
// Remove the canvas element from the chrome document.
var pageCanvas = this.doc_.getElementById(this.pageCanvasId_);
pageCanvas.parentNode.removeChild(pageCanvas);
// Hide the dimmer.
var dimarea = this.doc_.getElementById(this.dimAreaId_);
dimarea.hidden = true;
}
/**
* Helper class that periodically repaints the canvas. We repaint
* frequently at first, and then back off to a less frequent schedule
* at "steady state," and finally just stop altogether. We have to do
* this because we're not sure if the page has finished loading when
* we first paint the canvas, and because we want to reflect any
* dynamically written content into the canvas as it appears in the
* page after load.
*
* @param repaintFunc Function to call to repaint browser.
*
* @constructor
*/
function PROT_PhishMsgCanvasRepainter(repaintFunc) {
this.count_ = 0;
this.repaintFunc_ = repaintFunc;
this.initPeriodMS_ = 500; // Initially repaint every 500ms
this.steadyStateAtMS_ = 10 * 1000; // Go slowly after 10 seconds,
this.steadyStatePeriodMS_ = 3 * 1000; // repainting every 3 seconds, and
this.quitAtMS_ = 20 * 1000; // stop after 20 seconds
this.startMS_ = (new Date).getTime();
this.alarm_ = new G_Alarm(BindToObject(this.repaint, this),
this.initPeriodMS_);
}
/**
* Called periodically to repaint the canvas
*/
PROT_PhishMsgCanvasRepainter.prototype.repaint = function() {
this.repaintFunc_();
var nextRepaint;
// If we're in "steady state", use the slow repaint rate, else fast
if ((new Date).getTime() - this.startMS_ > this.steadyStateAtMS_)
nextRepaint = this.steadyStatePeriodMS_;
else
nextRepaint = this.initPeriodMS_;
if (!((new Date).getTime() - this.startMS_ > this.quitAtMS_))
this.alarm_ = new G_Alarm(BindToObject(this.repaint, this), nextRepaint);
}
/**
* Called to stop repainting the canvas
*/
PROT_PhishMsgCanvasRepainter.prototype.cancel = function() {
if (this.alarm_) {
this.alarm_.cancel();
this.alarm_ = null;
}
this.repaintFunc_ = null;
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// The warden checks request to see if they are for phishy pages. It
// does so by either querying a remote server with the URL (advanced
// protectoin mode) or querying our locally stored blacklists (privacy
// mode).
//
// When the warden notices a problem, it queries all browser views
// (each of which corresopnds to an open browser window) to see
// whether one of them can handle it. A browser view can handle a
// problem if its browser window has an HTMLDocument loaded with the
// given URL and that Document hasn't already been flagged as a
// problem. For every problematic URL we notice loading, at most one
// Document is flagged as problematic. Otherwise you can get into
// trouble if multiple concurrent phishy pages load with the same URL.
//
// Since we check URLs very early in the request cycle (in a progress
// listener), the URL might not yet be associated with a Document when
// we determine that it is phishy. So the the warden retries finding
// a browser view to handle the problem until one can, or until it
// determines it should give up (see complicated logic below).
//
// The warden has displayers that the browser view uses to render
// different kinds of warnings (e.g., one that's shown before a page
// loads as opposed to one that's shown after the page has already
// loaded).
//
// Note: There is a single warden for the whole application.
//
// TODO better way to expose displayers/views to browser view
const kPhishWardenEnabledPref = "browser.safebrowsing.enabled";
const kPhishWardenRemoteLookups = "browser.safebrowsing.remoteLookups";
// We have hardcoded URLs that we let people navigate to in order to
// check out the warning.
const kTestUrls = {
"http://www.google.com/tools/firefox/safebrowsing/phish-o-rama.html": true,
"http://www.mozilla.org/projects/bonecho/anti-phishing/its-a-trap.html": true,
"http://www.mozilla.com/firefox/its-a-trap.html": true,
}
/**
* Abtracts the checking of user/browser actions for signs of
* phishing.
*
* @param progressListener nsIDocNavStartProgressListener
* @constructor
*/
function PROT_PhishingWarden(progressListener) {
PROT_ListWarden.call(this);
this.debugZone = "phishwarden";
this.testing_ = false;
this.browserViews_ = [];
// Use this to query preferences
this.prefs_ = new G_Preferences();
// Only one displayer so far; perhaps we'll have others in the future
this.displayers_ = {
"afterload": PROT_PhishMsgDisplayer,
};
// We use this dude to do lookups on our remote server
this.fetcher_ = new PROT_TRFetcher();
// We need to know whether we're enabled and whether we're in advanced
// mode, so reflect the appropriate preferences into our state.
// Read state: should we be checking remote preferences?
this.checkRemote_ = this.prefs_.getPref(kPhishWardenRemoteLookups, null);
// true if we should use whitelists to suppress remote lookups
this.checkWhitelists_ = false;
// Get notifications when the remote check preference changes
var checkRemotePrefObserver = BindToObject(this.onCheckRemotePrefChanged,
this);
this.prefs_.addObserver(kPhishWardenRemoteLookups, checkRemotePrefObserver);
// Global preference to enable the phishing warden
this.phishWardenEnabled_ = this.prefs_.getPref(kPhishWardenEnabledPref, null);
// Get notifications when the phishing warden enabled pref changes
var phishWardenPrefObserver =
BindToObject(this.onPhishWardenEnabledPrefChanged, this);
this.prefs_.addObserver(kPhishWardenEnabledPref, phishWardenPrefObserver);
// Get notifications when the data provider pref changes
var dataProviderPrefObserver =
BindToObject(this.onDataProviderPrefChanged, this);
this.prefs_.addObserver(kDataProviderIdPref, dataProviderPrefObserver);
// hook up our browser listener
this.progressListener_ = progressListener;
this.progressListener_.callback = this;
this.progressListener_.enabled = this.phishWardenEnabled_;
// ms to wait after a request has started before firing JS callback
this.progressListener_.delay = 1500;
// object to keep track of request errors if we're in remote check mode
this.requestBackoff_ = new RequestBackoff(3 /* num errors */,
10*60*1000 /* error time, 10min */,
10*60*1000 /* backoff interval, 10min */,
6*60*60*1000 /* max backoff, 6hr */);
G_Debug(this, "phishWarden initialized");
}
PROT_PhishingWarden.inherits(PROT_ListWarden);
/**
* We implement nsIWebProgressListener
*/
PROT_PhishingWarden.prototype.QueryInterface = function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIWebProgressListener) ||
iid.equals(Ci.nsISupportsWeakReference))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
/**
* Cleanup on shutdown.
*/
PROT_PhishingWarden.prototype.shutdown = function() {
this.progressListener_.callback = null;
this.progressListener_ = null;
this.listManager_ = null;
}
/**
* When a preference (either advanced features or the phishwarden
* enabled) changes, we might have to start or stop asking for updates.
*
* This is a little tricky; we start or stop management only when we
* have complete information we can use to determine whether we
* should. It could be the case that one pref or the other isn't set
* yet (e.g., they haven't opted in/out of advanced features). So do
* nothing unless we have both pref values -- we get notifications for
* both, so eventually we will start correctly.
*/
PROT_PhishingWarden.prototype.maybeToggleUpdateChecking = function() {
if (this.testing_)
return;
var phishWardenEnabled = this.prefs_.getPref(kPhishWardenEnabledPref, null);
this.checkRemote_ = this.prefs_.getPref(kPhishWardenRemoteLookups, null);
G_Debug(this, "Maybe toggling update checking. " +
"Warden enabled? " + phishWardenEnabled + " || " +
"Check remote? " + this.checkRemote_);
// Do nothing unless both prefs are set. They can be null (unset), true, or
// false.
if (phishWardenEnabled === null || this.checkRemote_ === null)
return;
// We update and save to disk all tables if we don't have remote checking
// enabled.
if (phishWardenEnabled === true) {
// If anti-phishing is enabled, we always download the local files to
// use in case remote lookups fail.
this.enableBlacklistTableUpdates();
this.enableWhitelistTableUpdates();
if (this.checkRemote_ === true) {
// Remote lookup mode
// We check to see if the local list update host is the same as the
// remote lookup host. If they are the same, then we don't bother
// to do a remote url check if the url is in the whitelist.
var ioService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var updateHost = '';
var lookupHost = '';
try {
var url = ioService.newURI(gDataProvider.getUpdateURL(),
null, null);
updateHost = url.asciiHost;
} catch (e) { }
try {
var url = ioService.newURI(gDataProvider.getLookupURL(),
null, null);
lookupHost = url.asciiHost;
} catch (e) { }
if (updateHost && lookupHost && updateHost == lookupHost) {
// The data provider for local lists and remote lookups is the
// same, enable whitelist lookup suppression.
this.checkWhitelists_ = true;
} else {
// hosts don't match, don't use whitelist suppression
this.checkWhitelists_ = false;
}
}
} else {
// Anti-phishing is off, disable table updates
this.disableBlacklistTableUpdates();
this.disableWhitelistTableUpdates();
}
}
/**
* Controllers register their browser views with us
*
* @param view Reference to a browser view
*/
PROT_PhishingWarden.prototype.addBrowserView = function(view) {
G_Debug(this, "New browser view registered.");
this.browserViews_.push(view);
}
/**
* Controllers unregister their views when their window closes
*
* @param view Reference to a browser view
*/
PROT_PhishingWarden.prototype.removeBrowserView = function(view) {
for (var i = 0; i < this.browserViews_.length; i++)
if (this.browserViews_[i] === view) {
G_Debug(this, "Browser view unregistered.");
this.browserViews_.splice(i, 1);
return;
}
G_Assert(this, false, "Tried to unregister non-existent browser view!");
}
/**
* Deal with a user changing the pref that says whether we should check
* the remote server (i.e., whether we're in advanced mode)
*
* @param prefName Name of the pref holding the value indicating whether
* we should check remote server
*/
PROT_PhishingWarden.prototype.onCheckRemotePrefChanged = function(prefName) {
this.checkRemote_ = this.prefs_.getBoolPrefOrDefault(prefName,
this.checkRemote_);
this.requestBackoff_.reset();
this.maybeToggleUpdateChecking();
}
/**
* Deal with a user changing the pref that says whether we should
* enable the phishing warden (i.e., that SafeBrowsing is active)
*
* @param prefName Name of the pref holding the value indicating whether
* we should enable the phishing warden
*/
PROT_PhishingWarden.prototype.onPhishWardenEnabledPrefChanged = function(
prefName) {
this.phishWardenEnabled_ =
this.prefs_.getBoolPrefOrDefault(prefName, this.phishWardenEnabled_);
this.requestBackoff_.reset();
this.maybeToggleUpdateChecking();
this.progressListener_.enabled = this.phishWardenEnabled_;
}
/**
* Event fired when the user changes data providers.
*/
PROT_PhishingWarden.prototype.onDataProviderPrefChanged = function(prefName) {
// We want to reset request backoff state since it's a different provider.
this.requestBackoff_.reset();
// If we have a new data provider and we're doing remote lookups, then
// we may want to use whitelist lookup suppression or change which
// tables are being downloaded.
if (this.checkRemote_) {
this.maybeToggleUpdateChecking();
}
}
/**
* A request for a Document has been initiated somewhere. Check it!
*
* @param request
* @param url
*/
PROT_PhishingWarden.prototype.onDocNavStart = function(request, url) {
G_Debug(this, "checkRemote: " +
(this.checkRemote_ ? "yes" : "no"));
// If we're on a test page, trigger the warning.
// XXX Do we still need a test url or should each provider just put
// it in their local list?
if (this.isBlacklistTestURL(url)) {
this.houstonWeHaveAProblem_(request);
return;
}
// Make a remote lookup check if the pref is selected and if we haven't
// triggered server backoff. Otherwise, make a local check.
if (this.checkRemote_ && this.requestBackoff_.canMakeRequest()) {
// If we can use whitelists to suppress remote lookups, do so.
if (this.checkWhitelists_) {
var maybeRemoteCheck = BindToObject(this.maybeMakeRemoteCheck_,
this,
url,
request);
this.isWhiteURL(url, maybeRemoteCheck);
} else {
// Do a remote lookup (don't check whitelists)
this.fetcher_.get(url,
BindToObject(this.onTRFetchComplete,
this,
url,
request));
}
} else {
// Check the local lists for a match.
var evilCallback = BindToObject(this.localListMatch_,
this,
url,
request);
this.isEvilURL(url, evilCallback);
}
}
/**
* Callback from whitelist check when remote lookups is on.
* @param url String url to lookup
* @param request nsIRequest object
* @param status int enum from callback (PROT_ListWarden.IN_BLACKLIST,
* PROT_ListWarden.IN_WHITELIST, PROT_ListWarden.NOT_FOUND)
*/
PROT_PhishingWarden.prototype.maybeMakeRemoteCheck_ = function(url, request, status) {
if (PROT_ListWarden.IN_WHITELIST == status)
return;
G_Debug(this, "Local whitelist lookup failed");
this.fetcher_.get(url,
BindToObject(this.onTRFetchComplete,
this,
url,
request));
}
/**
* Invoked with the result of a lookupserver request.
*
* @param url String the URL we looked up
* @param request The nsIRequest in which we're interested
* @param trValues Object holding name/value pairs parsed from the
* lookupserver's response
* @param status Number HTTP status code or NS_ERROR_NOT_AVAILABLE if there's
* an HTTP error
*/
PROT_PhishingWarden.prototype.onTRFetchComplete = function(url,
request,
trValues,
status) {
// Did the remote http request succeed? If not, we fall back on
// local lists.
if (status == Components.results.NS_ERROR_NOT_AVAILABLE ||
this.requestBackoff_.isErrorStatus_(status)) {
this.requestBackoff_.noteServerResponse(status);
G_Debug(this, "remote check failed, using local lists instead");
var evilCallback = BindToObject(this.localListMatch_,
this,
url,
request);
this.isEvilURL(url, evilCallback);
} else {
var callback = BindToObject(this.houstonWeHaveAProblem_, this, request);
this.checkRemoteData(callback, trValues);
}
}
/**
* One of our Check* methods found a problem with a request. Why do we
* need to keep the nsIRequest (instead of just passing in the URL)?
* Because we need to know when to stop looking for the URL its
* fetching, and to know this we need the nsIRequest.isPending flag.
*
* @param request nsIRequest that is problematic
*/
PROT_PhishingWarden.prototype.houstonWeHaveAProblem_ = function(request) {
// We have a problem request that might or might not be associated
// with a Document that's currently in a browser. If it is, we
// want that Document. If it's not, we want to give it a chance to
// be loaded. See below for complete details.
if (this.maybeLocateProblem_(request)) // Cases 1 and 2 (see below)
return;
// OK, so the request isn't associated with any currently accessible
// Document, and we want to give it the chance to be. We don't want
// to retry forever (e.g., what if the Document was already displayed
// and navigated away from?), so we'll use nsIRequest.isPending to help
// us decide what to do.
//
// Aácomplication arises because there is a lag between when a
// request transitions from pending to not-pending and when it's
// associated with a Document in a browser. The transition from
// pending to not occurs just before the notification corresponding
// to NavWatcher.DOCNAVSTART (see NavWatcher), but the association
// occurs afterwards. Unfortunately, we're probably in DOCNAVSTART.
//
// Diagnosis by Darin:
// ---------------------------------------------------------------------------
// Here's a summary of what happens:
//
// RestorePresentation() {
// Dispatch_OnStateChange(dummy_request, STATE_START)
// PostCompletionEvent()
// }
//
// CompletionEvent() {
// ReallyRestorePresentation()
// Dispatch_OnStateChange(dummy_request, STATE_STOP)
// }
//
// So, now your code receives that initial OnStateChange event and sees
// that the dummy_request is not pending and not loaded in any window.
// So, you put a timeout(0) event in the queue. Then, the CompletionEvent
// is added to the queue. The stack unwinds....
//
// Your timeout runs, and you find that the dummy_request is still not
// pending and not loaded in any window. Then the CompletionEvent
// runs, and it hooks up the cached presentation.
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=319527
// ---------------------------------------------------------------------------
//
// So the logic is:
//
// request found an unhandled
// case pending? doc with the url? action
// ----------------------------------------------------------------
// 1 yes yes Use that doc (handled above)
// 2 no yes Use that doc (handled above)
// 3 yes no Retry
// 4 no no Retry twice (case described above)
//
// We don't get into trouble with Docs with the same URL "stealing" the
// warning because there is exactly one warning signaled per nav to
// a problem URL, and each Doc can be marked as problematic at most once.
if (request.isPending()) { // Case 3
G_Debug(this, "Can't find problem Doc; Req pending. Retrying.");
new G_Alarm(BindToObject(this.houstonWeHaveAProblem_,
this,
request),
200 /*ms*/);
} else { // Case 4
G_Debug(this,
"Can't find problem Doc; Req completed. Retrying at most twice.");
new G_ConditionalAlarm(BindToObject(this.maybeLocateProblem_,
this,
request),
0 /* next event loop */,
true /* repeat */,
2 /* at most twice */);
}
}
/**
* Query all browser views we know about and offer them the chance to
* handle the problematic request.
*
* @param request nsIRequest that is problematic
*
* @returns Boolean indicating if someone decided to handle it
*/
PROT_PhishingWarden.prototype.maybeLocateProblem_ = function(request) {
G_Debug(this, "Trying to find the problem.");
G_Debug(this, this.browserViews_.length + " browser views to check.");
for (var i = 0; i < this.browserViews_.length; i++) {
if (this.browserViews_[i].tryToHandleProblemRequest(this, request)) {
G_Debug(this, "Found browser view willing to handle problem!");
return true;
}
G_Debug(this, "wrong browser view");
}
return false;
}
/**
* Indicates if this URL is one of the possible blacklist test URLs.
* These test URLs should always be considered as phishy.
*
* @param url URL to check
* @return A boolean indicating whether this is one of our blacklist
* test URLs
*/
PROT_PhishingWarden.prototype.isBlacklistTestURL = function(url) {
// Explicitly check for URL so we don't get JS warnings in strict mode.
if (kTestUrls[url])
return true;
return false;
}
/**
* Callback for found local blacklist match. First we report that we have
* a blacklist hit, then we bring up the warning dialog.
* @param status Number enum from callback (PROT_ListWarden.IN_BLACKLIST,
* PROT_ListWarden.IN_WHITELIST, PROT_ListWarden.NOT_FOUND)
*/
PROT_PhishingWarden.prototype.localListMatch_ = function(url, request, status) {
if (PROT_ListWarden.IN_BLACKLIST != status)
return;
// Maybe send a report
(new PROT_Reporter).report("phishblhit", url);
this.houstonWeHaveAProblem_(request);
}
/**
* Examine data fetched from a lookup server for evidence of a
* phishing problem.
*
* @param callback Function to invoke if there is a problem.
* @param trValues Object containing name/value pairs the server returned
*/
PROT_PhishingWarden.prototype.checkRemoteData = function(callback,
trValues) {
if (!trValues) {
G_Debug(this, "Didn't get TR values from the server.");
return;
}
G_Debug(this, "Page has phishiness " + trValues["phishy"]);
if (trValues["phishy"] == 1) { // It's on our blacklist
G_Debug(this, "Remote blacklist hit");
callback(this);
} else {
G_Debug(this, "Remote blacklist miss");
}
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A tiny class to do reporting for us. We report interesting user actions
// such as the user hitting a blacklisted page, and the user accepting
// or declining the warning.
//
// Each report has a subject and data. Current reports are:
//
// subject data meaning
// --------------------------------
// phishnavaway url the user navigated away from a phishy page
// phishdecline url the user declined our warning
// phishaccept url the user accepted our warning
// phishblhit url the user loaded a phishing page
//
// We only send reports in advanced protection mode, and even then we
// strip cookies from the request before sending it.
/**
* A very complicated class to send pings to the provider. The class does
* nothing if we're not in advanced protection mode.
*
* @constructor
*/
function PROT_Reporter() {
this.debugZone = "reporter";
this.prefs_ = new G_Preferences();
}
/**
* Send a report!
*
* @param subject String indicating what this report is about (will be
* urlencoded)
* @param data String giving extra information about this report (will be
* urlencoded)
*/
PROT_Reporter.prototype.report = function(subject, data) {
// Send a report iff we're in advanced protection mode
if (!this.prefs_.getPref(kPhishWardenRemoteLookups, false))
return;
// Make sure a report url is defined
var url = gDataProvider.getReportURL();
// Report url is optional, so we just ignore the request if a report
// url isn't provided.
if (!url)
return;
url += "evts=" + encodeURIComponent(subject)
+ "&evtd=" + encodeURIComponent(data);
G_Debug(this, "Sending report: " + url);
(new PROT_XMLFetcher(true /* strip cookies */)).get(url, null /* no cb */);
}
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A helper class that does "trustrank" lookups on a remote
// server. Right now this lookup just indicates if a page is
// phishing. The response format is protocol4 name/value pairs.
//
// Since we're sending full URLs to the server, we try to encrypt
// them before transmission. Else HTTPS query params could leak.
/**
* A helper class that fetches trustrank values, parses them, and
* passes them via an object to a callback.
*
* @constructor
*/
function PROT_TRFetcher(opt_noCrypto) {
this.debugZone = "trfetcher";
this.useCrypto_ = !opt_noCrypto;
this.protocol4Parser_ = new G_Protocol4Parser();
// We lazily instantiate the UrlCrypto object due to:
// https://bugzilla.mozilla.org/show_bug.cgi?id=321024
//
// Otherwise here we would use:
// this.urlCrypto_ = new PROT_UrlCrypto();
}
PROT_TRFetcher.TRY_REKEYING_RESPONSE = "pleaserekey";
/**
* Get the URL of the request that will fetch us TR for the argument URL
*
* @param url String containing the URL we'd like to fetch info about
*
* @returns String containing the url we should use to fetch tr info
*/
PROT_TRFetcher.prototype.getRequestURL_ = function(url) {
if (!this.urlCrypto_)
this.urlCrypto_ = new PROT_UrlCrypto();
G_Debug(this, "Fetching for " + url);
var requestURL = gDataProvider.getLookupURL();
if (!requestURL)
return null;
if (this.useCrypto_) {
var maybeCryptedParams = this.urlCrypto_.maybeCryptParams({ "q": url});
for (var param in maybeCryptedParams)
requestURL += param + "=" +
encodeURIComponent(maybeCryptedParams[param]) + "&";
} else {
requestURL += "q=" + encodeURIComponent(url);
}
G_Debug(this, "Request URL: " + requestURL);
return requestURL;
};
/**
* Fetches information about a page.
*
* @param forPage URL for which to fetch info
*
* @param callback Function to call back when complete.
*/
PROT_TRFetcher.prototype.get = function(forPage, callback) {
var url = this.getRequestURL_(forPage);
if (!url) {
G_Debug(this, "No remote lookup url.");
return;
}
var closure = BindToObject(this.onFetchComplete_, this, callback);
(new PROT_XMLFetcher()).get(url, closure);
};
/**
* Invoked when a fetch has completed.
*
* @param callback Function to invoke with parsed response object
* @param responseText Text of the protocol4 message
* @param httpStatus Number HTTP status code or NS_ERROR_NOT_AVAILABLE if the
* request failed
*/
PROT_TRFetcher.prototype.onFetchComplete_ = function(callback, responseText,
httpStatus) {
var responseObj = this.extractResponse_(responseText);
// The server might tell us to rekey, for example if it sees that
// our request was unencrypted (meaning that we might not yet have
// a key). If so, pass this hint along to the crypto key manager.
if (responseObj[PROT_TRFetcher.TRY_REKEYING_RESPONSE] == "1" &&
this.urlCrypto_) {
G_Debug(this, "We're supposed to re-key. Trying.");
var manager = this.urlCrypto_.getManager();
if (manager)
manager.maybeReKey();
}
G_Debug(this, "TR Response:");
for (var field in responseObj)
G_Debug(this, field + "=" + responseObj[field]);
callback(responseObj, httpStatus);
};
/**
* Parse a protocol4 message (lookup server response)
*
* @param responseText String containing the server's response
*
* @returns Object containing the returned values or null if no
* response was received
*/
PROT_TRFetcher.prototype.extractResponse_ = function(responseText) {
return this.protocol4Parser_.parse(responseText);
};
//@line 27 "/cygdrive/K/tinderbuild/src/flock/mozilla/browser/components/safebrowsing/src/nsSafebrowsingApplication.js"
var modScope = this;
function Init() {
var jslib = Cc["@mozilla.org/url-classifier/jslib;1"]
.getService().wrappedJSObject;
modScope.String.prototype.startsWith = jslib.String.prototype.startsWith;
modScope.G_Debug = jslib.G_Debug;
modScope.G_Assert = jslib.G_Assert;
modScope.G_Alarm = jslib.G_Alarm;
modScope.G_ConditionalAlarm = jslib.G_ConditionalAlarm;
modScope.G_ObserverWrapper = jslib.G_ObserverWrapper;
modScope.G_Preferences = jslib.G_Preferences;
modScope.PROT_XMLFetcher = jslib.PROT_XMLFetcher;
modScope.BindToObject = jslib.BindToObject;
modScope.G_Protocol4Parser = jslib.G_Protocol4Parser;
modScope.G_ObjectSafeMap = jslib.G_ObjectSafeMap;
modScope.PROT_UrlCrypto = jslib.PROT_UrlCrypto;
modScope.RequestBackoff = jslib.RequestBackoff;
// We only need to call Init once
modScope.Init = function() {};
}
// Module object
function SafebrowsingApplicationMod() {
this.firstTime = true;
this.cid = Components.ID("{c64d0bcb-8270-4ca7-a0b3-3380c8ffecb5}");
this.progid = "@mozilla.org/safebrowsing/application;1";
}
SafebrowsingApplicationMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) {
if (this.firstTime) {
this.firstTime = false;
throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
}
compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
compMgr.registerFactoryLocation(this.cid,
"Safebrowsing Application Module",
this.progid,
fileSpec,
loc,
type);
};
SafebrowsingApplicationMod.prototype.getClassObject = function(compMgr, cid, iid) {
if (!cid.equals(this.cid))
throw Components.results.NS_ERROR_NO_INTERFACE;
if (!iid.equals(Ci.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
return this.factory;
}
SafebrowsingApplicationMod.prototype.canUnload = function(compMgr) {
return true;
}
SafebrowsingApplicationMod.prototype.factory = {
createInstance: function(outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
Init();
return new PROT_Application();
}
};
var ApplicationModInst = new SafebrowsingApplicationMod();
function NSGetModule(compMgr, fileSpec) {
return ApplicationModInst;
}